Browse Source

feat: change Jupyter cwd alone with "bash" (#3331)

* remove unused plugin mixin

* change the entire jupyter PWD with bash;
print jupyter pwd in obs as well;

* remove unused field

* remove unused comments

* change the entire jupyter PWD with bash;
print jupyter pwd in obs as well;

* fix runtime tests for jupyter

* update intgeration tests

* fix test again

---------

Co-authored-by: Graham Neubig <neubig@gmail.com>
Xingyao Wang 1 year ago
parent
commit
568e6cdb40

+ 3 - 1
opendevin/runtime/client/client.py

@@ -301,7 +301,7 @@ class RuntimeClient:
                 logger.debug(
                 logger.debug(
                     f"{self.pwd} != {getattr(self, '_jupyter_pwd', None)} -> reset Jupyter PWD"
                     f"{self.pwd} != {getattr(self, '_jupyter_pwd', None)} -> reset Jupyter PWD"
                 )
                 )
-                reset_jupyter_pwd_code = f'import os; os.environ["JUPYTER_PWD"] = os.path.abspath("{self.pwd}")'
+                reset_jupyter_pwd_code = f'import os; os.chdir("{self.pwd}")'
                 _aux_action = IPythonRunCellAction(code=reset_jupyter_pwd_code)
                 _aux_action = IPythonRunCellAction(code=reset_jupyter_pwd_code)
                 _reset_obs = await _jupyter_plugin.run(_aux_action)
                 _reset_obs = await _jupyter_plugin.run(_aux_action)
                 logger.debug(
                 logger.debug(
@@ -310,6 +310,8 @@ class RuntimeClient:
                 self._jupyter_pwd = self.pwd
                 self._jupyter_pwd = self.pwd
 
 
             obs: IPythonRunCellObservation = await _jupyter_plugin.run(action)
             obs: IPythonRunCellObservation = await _jupyter_plugin.run(action)
+            obs.content = obs.content.rstrip()
+            obs.content += f'\n[Jupyter current working directory: {self.pwd}]'
             return obs
             return obs
         else:
         else:
             raise RuntimeError(
             raise RuntimeError(

+ 0 - 65
opendevin/runtime/plugins/agent_skills/agentskills.py

@@ -17,7 +17,6 @@ Functions:
 """
 """
 
 
 import base64
 import base64
-import functools
 import os
 import os
 import re
 import re
 import shutil
 import shutil
@@ -75,52 +74,6 @@ def _get_openai_client():
 # ==================================================================================================
 # ==================================================================================================
 
 
 
 
-# Define the decorator using the functionality of UpdatePwd
-def update_pwd_decorator(func):
-    @functools.wraps(func)
-    def wrapper(*args, **kwargs):
-        jupyter_pwd = os.environ.get('JUPYTER_PWD', None)
-        try:
-            old_pwd = os.getcwd()
-        except FileNotFoundError:
-            import json
-            import subprocess
-
-            print(
-                f'DEBUGGING Environment variables: {json.dumps(dict(os.environ), indent=2)}'
-            )
-            print(f'DEBUGGING User ID: {os.getuid()}, Group ID: {os.getgid()}')
-
-            out = subprocess.run(['pwd'], capture_output=True)
-            old_pwd = out.stdout.decode('utf-8').strip()
-            os.chdir(old_pwd)
-            print(f'DEBUGGING Change to working directory: {old_pwd}')
-
-            import tempfile
-
-            try:
-                tempfile.TemporaryFile(dir=old_pwd)
-                print(f'DEBUGGING Directory {old_pwd} is writable')
-            except Exception as e:
-                print(f'DEBUGGING Directory {old_pwd} is not writable: {str(e)}')
-
-            # ls -alh
-            out = subprocess.run(['ls', '-alh', old_pwd], capture_output=True)
-            print(
-                f'DEBUGGING OLD working directory contents: {out.stdout.decode("utf-8")}'
-            )
-            print(f'DEBUGGING Target JUPYTER pwd: {jupyter_pwd}')
-
-        if jupyter_pwd:
-            os.chdir(jupyter_pwd)
-        try:
-            return func(*args, **kwargs)
-        finally:
-            os.chdir(old_pwd)
-
-    return wrapper
-
-
 def _is_valid_filename(file_name) -> bool:
 def _is_valid_filename(file_name) -> bool:
     if not file_name or not isinstance(file_name, str) or not file_name.strip():
     if not file_name or not isinstance(file_name, str) or not file_name.strip():
         return False
         return False
@@ -240,7 +193,6 @@ def _cur_file_header(current_file, total_lines) -> str:
     return f'[File: {os.path.abspath(current_file)} ({total_lines} lines total)]\n'
     return f'[File: {os.path.abspath(current_file)} ({total_lines} lines total)]\n'
 
 
 
 
-@update_pwd_decorator
 def open_file(
 def open_file(
     path: str, line_number: int | None = 1, context_lines: int | None = WINDOW
     path: str, line_number: int | None = 1, context_lines: int | None = WINDOW
 ) -> None:
 ) -> None:
@@ -277,7 +229,6 @@ def open_file(
     print(output)
     print(output)
 
 
 
 
-@update_pwd_decorator
 def goto_line(line_number: int) -> None:
 def goto_line(line_number: int) -> None:
     """Moves the window to show the specified line number.
     """Moves the window to show the specified line number.
 
 
@@ -299,7 +250,6 @@ def goto_line(line_number: int) -> None:
     print(output)
     print(output)
 
 
 
 
-@update_pwd_decorator
 def scroll_down() -> None:
 def scroll_down() -> None:
     """Moves the window down by 100 lines.
     """Moves the window down by 100 lines.
 
 
@@ -317,7 +267,6 @@ def scroll_down() -> None:
     print(output)
     print(output)
 
 
 
 
-@update_pwd_decorator
 def scroll_up() -> None:
 def scroll_up() -> None:
     """Moves the window up by 100 lines.
     """Moves the window up by 100 lines.
 
 
@@ -335,7 +284,6 @@ def scroll_up() -> None:
     print(output)
     print(output)
 
 
 
 
-@update_pwd_decorator
 def create_file(filename: str) -> None:
 def create_file(filename: str) -> None:
     """Creates and opens a new file with the given name.
     """Creates and opens a new file with the given name.
 
 
@@ -647,7 +595,6 @@ def _edit_file_impl(
     return ret_str
     return ret_str
 
 
 
 
-@update_pwd_decorator
 def edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
 def edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
     """Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
     """Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
 
 
@@ -749,7 +696,6 @@ def edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> N
     print(ret_str)
     print(ret_str)
 
 
 
 
-@update_pwd_decorator
 def insert_content_at_line(file_name: str, line_number: int, content: str) -> None:
 def insert_content_at_line(file_name: str, line_number: int, content: str) -> None:
     """Insert content at the given line number in a file.
     """Insert content at the given line number in a file.
     This will NOT modify the content of the lines before OR after the given line number.
     This will NOT modify the content of the lines before OR after the given line number.
@@ -784,7 +730,6 @@ def insert_content_at_line(file_name: str, line_number: int, content: str) -> No
     print(ret_str)
     print(ret_str)
 
 
 
 
-@update_pwd_decorator
 def append_file(file_name: str, content: str) -> None:
 def append_file(file_name: str, content: str) -> None:
     """Append content to the given file.
     """Append content to the given file.
     It appends text `content` to the end of the specified file.
     It appends text `content` to the end of the specified file.
@@ -805,7 +750,6 @@ def append_file(file_name: str, content: str) -> None:
     print(ret_str)
     print(ret_str)
 
 
 
 
-@update_pwd_decorator
 def search_dir(search_term: str, dir_path: str = './') -> None:
 def search_dir(search_term: str, dir_path: str = './') -> None:
     """Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
     """Searches for search_term in all files in dir. If dir is not provided, searches in the current directory.
 
 
@@ -845,7 +789,6 @@ def search_dir(search_term: str, dir_path: str = './') -> None:
     print(f'[End of matches for "{search_term}" in {dir_path}]')
     print(f'[End of matches for "{search_term}" in {dir_path}]')
 
 
 
 
-@update_pwd_decorator
 def search_file(search_term: str, file_path: Optional[str] = None) -> None:
 def search_file(search_term: str, file_path: Optional[str] = None) -> None:
     """Searches for search_term in file. If file is not provided, searches in the current open file.
     """Searches for search_term in file. If file is not provided, searches in the current open file.
 
 
@@ -878,7 +821,6 @@ def search_file(search_term: str, file_path: Optional[str] = None) -> None:
         print(f'[No matches found for "{search_term}" in {file_path}]')
         print(f'[No matches found for "{search_term}" in {file_path}]')
 
 
 
 
-@update_pwd_decorator
 def find_file(file_name: str, dir_path: str = './') -> None:
 def find_file(file_name: str, dir_path: str = './') -> None:
     """Finds all files with the given name in the specified directory.
     """Finds all files with the given name in the specified directory.
 
 
@@ -904,7 +846,6 @@ def find_file(file_name: str, dir_path: str = './') -> None:
         print(f'[No matches found for "{file_name}" in {dir_path}]')
         print(f'[No matches found for "{file_name}" in {dir_path}]')
 
 
 
 
-@update_pwd_decorator
 def parse_pdf(file_path: str) -> None:
 def parse_pdf(file_path: str) -> None:
     """Parses the content of a PDF file and prints it.
     """Parses the content of a PDF file and prints it.
 
 
@@ -923,7 +864,6 @@ def parse_pdf(file_path: str) -> None:
     print(text.strip())
     print(text.strip())
 
 
 
 
-@update_pwd_decorator
 def parse_docx(file_path: str) -> None:
 def parse_docx(file_path: str) -> None:
     """Parses the content of a DOCX file and prints it.
     """Parses the content of a DOCX file and prints it.
 
 
@@ -938,7 +878,6 @@ def parse_docx(file_path: str) -> None:
     print(text)
     print(text)
 
 
 
 
-@update_pwd_decorator
 def parse_latex(file_path: str) -> None:
 def parse_latex(file_path: str) -> None:
     """Parses the content of a LaTex file and prints it.
     """Parses the content of a LaTex file and prints it.
 
 
@@ -991,7 +930,6 @@ def _prepare_image_messages(task: str, base64_image: str):
     ]
     ]
 
 
 
 
-@update_pwd_decorator
 def parse_audio(file_path: str, model: str = 'whisper-1') -> None:
 def parse_audio(file_path: str, model: str = 'whisper-1') -> None:
     """Parses the content of an audio file and prints it.
     """Parses the content of an audio file and prints it.
 
 
@@ -1012,7 +950,6 @@ def parse_audio(file_path: str, model: str = 'whisper-1') -> None:
         print(f'Error transcribing audio file: {e}')
         print(f'Error transcribing audio file: {e}')
 
 
 
 
-@update_pwd_decorator
 def parse_image(
 def parse_image(
     file_path: str, task: str = 'Describe this image as detail as possible.'
     file_path: str, task: str = 'Describe this image as detail as possible.'
 ) -> None:
 ) -> None:
@@ -1038,7 +975,6 @@ def parse_image(
         print(f'Error with the request: {error}')
         print(f'Error with the request: {error}')
 
 
 
 
-@update_pwd_decorator
 def parse_video(
 def parse_video(
     file_path: str,
     file_path: str,
     task: str = 'Describe this image as detail as possible.',
     task: str = 'Describe this image as detail as possible.',
@@ -1086,7 +1022,6 @@ def parse_video(
             print(f'Error with the request: {error}')
             print(f'Error with the request: {error}')
 
 
 
 
-@update_pwd_decorator
 def parse_pptx(file_path: str) -> None:
 def parse_pptx(file_path: str) -> None:
     """Parses the content of a pptx file and prints it.
     """Parses the content of a pptx file and prints it.
 
 

+ 2 - 0
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython/prompt_001.log

@@ -396,6 +396,8 @@ The server is running on port 5000 with PID 126. You can access the list of numb
 
 
 NOW, LET'S START!
 NOW, LET'S START!
 
 
+----------
+
 Use Jupyter IPython to write a text file containing 'hello world' to '/workspace/test.txt'. Do not ask me for confirmation at any point.
 Use Jupyter IPython to write a text file containing 'hello world' to '/workspace/test.txt'. Do not ask me for confirmation at any point.
 
 
 ENVIRONMENT REMINDER: You have 14 turns left to complete the task. When finished reply with <finish></finish>.
 ENVIRONMENT REMINDER: You have 14 turns left to complete the task. When finished reply with <finish></finish>.

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

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

+ 2 - 0
tests/integration/mock/eventstream_runtime/CodeActAgent/test_ipython_module/prompt_001.log

@@ -396,6 +396,8 @@ The server is running on port 5000 with PID 126. You can access the list of numb
 
 
 NOW, LET'S START!
 NOW, LET'S START!
 
 
+----------
+
 Install and import pymsgbox==1.0.9 and print it's version in /workspace/test.txt. Do not ask me for confirmation at any point.
 Install and import pymsgbox==1.0.9 and print it's version in /workspace/test.txt. Do not ask me for confirmation at any point.
 
 
 ENVIRONMENT REMINDER: You have 14 turns left to complete the task. When finished reply with <finish></finish>.
 ENVIRONMENT REMINDER: You have 14 turns left to complete the task. When finished reply with <finish></finish>.

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

@@ -412,12 +412,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 OBSERVATION:
 Collecting pymsgbox==1.0.9
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
   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  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=7cc4ff18827e3c2e2bd0780a92ca70fd625dc7645b994b6f3191dfb0660e606e
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=0aa91e03ed30fdcf42db3522fd0444c80951cccb215529a6ed9a95fc2b8a6f0c
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
 Installing collected packages: pymsgbox
@@ -426,6 +426,6 @@ Successfully installed pymsgbox-1.0.9
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] To update, run: pip install --upgrade pip
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 Note: you may need to restart the kernel to use updated packages.
+[Jupyter current working directory: /workspace]
 
 
-
-ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>
+ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>.

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

@@ -412,12 +412,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 OBSERVATION:
 Collecting pymsgbox==1.0.9
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
   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  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=7cc4ff18827e3c2e2bd0780a92ca70fd625dc7645b994b6f3191dfb0660e606e
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=0aa91e03ed30fdcf42db3522fd0444c80951cccb215529a6ed9a95fc2b8a6f0c
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
 Installing collected packages: pymsgbox
@@ -426,7 +426,7 @@ Successfully installed pymsgbox-1.0.9
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] To update, run: pip install --upgrade pip
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 Note: you may need to restart the kernel to use updated packages.
-
+[Jupyter current working directory: /workspace]
 
 
 ----------
 ----------
 
 
@@ -444,5 +444,6 @@ with open('/workspace/test.txt', 'w') as file:
 
 
 OBSERVATION:
 OBSERVATION:
 [Code executed successfully with no output]
 [Code executed successfully with no output]
+[Jupyter current working directory: /workspace]
 
 
-ENVIRONMENT REMINDER: You have 12 turns left to complete the task. When finished reply with <finish></finish>
+ENVIRONMENT REMINDER: You have 12 turns left to complete the task. When finished reply with <finish></finish>.

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

@@ -412,12 +412,12 @@ Sure! Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 OBSERVATION:
 Collecting pymsgbox==1.0.9
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
   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  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=7cc4ff18827e3c2e2bd0780a92ca70fd625dc7645b994b6f3191dfb0660e606e
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=0aa91e03ed30fdcf42db3522fd0444c80951cccb215529a6ed9a95fc2b8a6f0c
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
 Installing collected packages: pymsgbox
@@ -426,7 +426,7 @@ Successfully installed pymsgbox-1.0.9
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] To update, run: pip install --upgrade pip
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 Note: you may need to restart the kernel to use updated packages.
-
+[Jupyter current working directory: /workspace]
 
 
 ----------
 ----------
 
 
@@ -444,6 +444,7 @@ with open('/workspace/test.txt', 'w') as file:
 
 
 OBSERVATION:
 OBSERVATION:
 [Code executed successfully with no output]
 [Code executed successfully with no output]
+[Jupyter current working directory: /workspace]
 
 
 ----------
 ----------
 
 
@@ -460,4 +461,4 @@ pymsgbox version: 1.0.9
 opendevin@docker-desktop:/workspace $
 opendevin@docker-desktop:/workspace $
 [Command -1 finished with exit code 0]
 [Command -1 finished with exit code 0]
 
 
-ENVIRONMENT REMINDER: You have 11 turns left to complete the task. When finished reply with <finish></finish>
+ENVIRONMENT REMINDER: You have 11 turns left to complete the task. When finished reply with <finish></finish>.

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

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

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

@@ -603,12 +603,12 @@ Understood. Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 OBSERVATION:
 Collecting pymsgbox==1.0.9
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
   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  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e504d1b40c05b7da59bba1f908d23edcd98381d8e0ecc41a1162745ee4ee6fd2
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e6c344aecd9e7b02d3ff2bb4d98a74d0fe6156b5f523d40b402350da7aac55e6
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
 Installing collected packages: pymsgbox
@@ -617,6 +617,6 @@ Successfully installed pymsgbox-1.0.9
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] To update, run: pip install --upgrade pip
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 Note: you may need to restart the kernel to use updated packages.
-
+[Jupyter current working directory: /workspace]
 
 
 ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>.
 ENVIRONMENT REMINDER: You have 13 turns left to complete the task. When finished reply with <finish></finish>.

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

@@ -603,12 +603,12 @@ Understood. Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 OBSERVATION:
 Collecting pymsgbox==1.0.9
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
   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  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e504d1b40c05b7da59bba1f908d23edcd98381d8e0ecc41a1162745ee4ee6fd2
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e6c344aecd9e7b02d3ff2bb4d98a74d0fe6156b5f523d40b402350da7aac55e6
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
 Installing collected packages: pymsgbox
@@ -617,7 +617,7 @@ Successfully installed pymsgbox-1.0.9
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] To update, run: pip install --upgrade pip
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 Note: you may need to restart the kernel to use updated packages.
-
+[Jupyter current working directory: /workspace]
 
 
 ----------
 ----------
 
 
@@ -637,5 +637,6 @@ version_info
 
 
 OBSERVATION:
 OBSERVATION:
 '1.0.9'
 '1.0.9'
+[Jupyter current working directory: /workspace]
 
 
 ENVIRONMENT REMINDER: You have 12 turns left to complete the task. When finished reply with <finish></finish>.
 ENVIRONMENT REMINDER: You have 12 turns left to complete the task. When finished reply with <finish></finish>.

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

@@ -603,12 +603,12 @@ Understood. Let's start by installing the `pymsgbox` package.
 OBSERVATION:
 OBSERVATION:
 Collecting pymsgbox==1.0.9
 Collecting pymsgbox==1.0.9
   Downloading PyMsgBox-1.0.9.tar.gz (18 kB)
   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  Getting requirements to build wheel ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25h  Preparing metadata (pyproject.toml) ... [?25l- done
 [?25hBuilding wheels for collected packages: pymsgbox
 [?25hBuilding wheels for collected packages: pymsgbox
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
   Building wheel for pymsgbox (pyproject.toml) ... [?25l- done
-[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e504d1b40c05b7da59bba1f908d23edcd98381d8e0ecc41a1162745ee4ee6fd2
+[?25h  Created wheel for pymsgbox: filename=PyMsgBox-1.0.9-py3-none-any.whl size=7406 sha256=e6c344aecd9e7b02d3ff2bb4d98a74d0fe6156b5f523d40b402350da7aac55e6
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
   Stored in directory: /home/opendevin/.cache/pip/wheels/85/92/63/e126ee5f33d8f2ed04f96e43ef5df7270a2f331848752e8662
 Successfully built pymsgbox
 Successfully built pymsgbox
 Installing collected packages: pymsgbox
 Installing collected packages: pymsgbox
@@ -617,7 +617,7 @@ Successfully installed pymsgbox-1.0.9
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] A new release of pip is available: 24.1 -> 24.2
 [notice] To update, run: pip install --upgrade pip
 [notice] To update, run: pip install --upgrade pip
 Note: you may need to restart the kernel to use updated packages.
 Note: you may need to restart the kernel to use updated packages.
-
+[Jupyter current working directory: /workspace]
 
 
 ----------
 ----------
 
 
@@ -637,6 +637,7 @@ version_info
 
 
 OBSERVATION:
 OBSERVATION:
 '1.0.9'
 '1.0.9'
+[Jupyter current working directory: /workspace]
 
 
 ----------
 ----------
 
 

+ 23 - 5
tests/unit/test_runtime.py

@@ -303,7 +303,10 @@ async def test_simple_cmd_ipython_and_fileop(temp_dir, box_class, run_as_devin):
     assert isinstance(obs, IPythonRunCellObservation)
     assert isinstance(obs, IPythonRunCellObservation)
 
 
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
-    assert obs.content.strip() == 'Hello, `World`!'
+    assert (
+        obs.content.strip()
+        == 'Hello, `World`!\n[Jupyter current working directory: /workspace]'
+    )
 
 
     # Test read file (file should not exist)
     # Test read file (file should not exist)
     action_read = FileReadAction(path='hello.sh')
     action_read = FileReadAction(path='hello.sh')
@@ -768,7 +771,10 @@ async def test_ipython_multi_user(temp_dir, box_class, run_as_devin):
     obs = await runtime.run_action(action_ipython)
     obs = await runtime.run_action(action_ipython)
     assert isinstance(obs, IPythonRunCellObservation)
     assert isinstance(obs, IPythonRunCellObservation)
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
-    assert obs.content.strip() == '/workspace'
+    assert (
+        obs.content.strip()
+        == '/workspace\n[Jupyter current working directory: /workspace]'
+    )
 
 
     # write a file
     # write a file
     test_code = "with open('test.txt', 'w') as f: f.write('Hello, world!')"
     test_code = "with open('test.txt', 'w') as f: f.write('Hello, world!')"
@@ -777,7 +783,10 @@ async def test_ipython_multi_user(temp_dir, box_class, run_as_devin):
     obs = await runtime.run_action(action_ipython)
     obs = await runtime.run_action(action_ipython)
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     assert isinstance(obs, IPythonRunCellObservation)
     assert isinstance(obs, IPythonRunCellObservation)
-    assert obs.content.strip() == '[Code executed successfully with no output]'
+    assert (
+        obs.content.strip()
+        == '[Code executed successfully with no output]\n[Jupyter current working directory: /workspace]'
+    )
 
 
     # check file owner via bash
     # check file owner via bash
     action = CmdRunAction(command='ls -alh test.txt')
     action = CmdRunAction(command='ls -alh test.txt')
@@ -816,7 +825,7 @@ async def test_ipython_simple(temp_dir, box_class):
     obs = await runtime.run_action(action_ipython)
     obs = await runtime.run_action(action_ipython)
     assert isinstance(obs, IPythonRunCellObservation)
     assert isinstance(obs, IPythonRunCellObservation)
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
-    assert obs.content.strip() == '1'
+    assert obs.content.strip() == '1\n[Jupyter current working directory: /workspace]'
 
 
     await runtime.close()
     await runtime.close()
     await asyncio.sleep(1)
     await asyncio.sleep(1)
@@ -850,6 +859,7 @@ async def _test_ipython_agentskills_fileop_pwd_impl(
         '1|\n'
         '1|\n'
         '(this is the end of the file)\n'
         '(this is the end of the file)\n'
         '[File hello.py created.]\n'
         '[File hello.py created.]\n'
+        '[Jupyter current working directory: /workspace]'
     ).strip().split('\n')
     ).strip().split('\n')
 
 
     action = CmdRunAction(command='cd test')
     action = CmdRunAction(command='cd test')
@@ -872,6 +882,7 @@ async def _test_ipython_agentskills_fileop_pwd_impl(
         '1|\n'
         '1|\n'
         '(this is the end of the file)\n'
         '(this is the end of the file)\n'
         '[File hello.py created.]\n'
         '[File hello.py created.]\n'
+        '[Jupyter current working directory: /workspace/test]'
     ).strip().split('\n')
     ).strip().split('\n')
 
 
     if enable_auto_lint:
     if enable_auto_lint:
@@ -904,6 +915,7 @@ ERRORS:
 Your changes have NOT been applied. Please fix your edit command and try again.
 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.
 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.
 DO NOT re-run the same failed edit command. Running it again will lead to the same error.
+[Jupyter current working directory: /workspace/test]
 """
 """
         ).strip().split('\n')
         ).strip().split('\n')
 
 
@@ -922,6 +934,7 @@ DO NOT re-run the same failed edit command. Running it again will lead to the sa
 1|print("hello world")
 1|print("hello world")
 (this is the end of the file)
 (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.]
 [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]
 """
 """
     ).strip().split('\n')
     ).strip().split('\n')
 
 
@@ -988,6 +1001,7 @@ async def test_ipython_agentskills_fileop_pwd_with_userdir(temp_dir, box_class):
         '1|\n'
         '1|\n'
         '(this is the end of the file)\n'
         '(this is the end of the file)\n'
         '[File hello.py created.]\n'
         '[File hello.py created.]\n'
+        '[Jupyter current working directory: /root]'
     ).strip().split('\n')
     ).strip().split('\n')
 
 
     action = CmdRunAction(command='cd test')
     action = CmdRunAction(command='cd test')
@@ -1010,6 +1024,7 @@ async def test_ipython_agentskills_fileop_pwd_with_userdir(temp_dir, box_class):
         '1|\n'
         '1|\n'
         '(this is the end of the file)\n'
         '(this is the end of the file)\n'
         '[File hello.py created.]\n'
         '[File hello.py created.]\n'
+        '[Jupyter current working directory: /root/test]'
     ).strip().split('\n')
     ).strip().split('\n')
 
 
     await runtime.close()
     await runtime.close()
@@ -1073,7 +1088,10 @@ async def test_ipython_package_install(temp_dir, box_class, run_as_devin):
     obs = await runtime.run_action(action)
     obs = await runtime.run_action(action)
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     logger.info(obs, extra={'msg_type': 'OBSERVATION'})
     # import should not error out
     # import should not error out
-    assert obs.content.strip() == '[Code executed successfully with no output]'
+    assert (
+        obs.content.strip()
+        == '[Code executed successfully with no output]\n[Jupyter current working directory: /workspace]'
+    )
 
 
     await runtime.close()
     await runtime.close()
     await asyncio.sleep(1)
     await asyncio.sleep(1)