Эх сурвалжийг харах

Document various runtimes (#4536)

Co-authored-by: Xingyao Wang <xingyao@all-hands.dev>
Robert Brennan 1 жил өмнө
parent
commit
2e50a5bef5

+ 1 - 1
.github/workflows/ghcr-build.yml

@@ -1,5 +1,5 @@
 # Workflow that builds, tests and then pushes the OpenHands and runtime docker images to the ghcr.io repository
-name: Build, Test and Publish RT Image
+name: Docker
 
 # Always run on "main"
 # Always run on tags

+ 11 - 13
README.md

@@ -40,30 +40,28 @@ See the [Installation](https://docs.all-hands.dev/modules/usage/installation) gu
 system requirements and more information.
 
 ```bash
-export WORKSPACE_BASE=$(pwd)/workspace
+docker pull docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik
 
-docker pull ghcr.io/all-hands-ai/runtime:0.11-nikolaik
-
-docker run -it --pull=always \
-    -e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.11-nikolaik \
-    -e SANDBOX_USER_ID=$(id -u) \
-    -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-    -v $WORKSPACE_BASE:/opt/workspace_base \
+docker run -it --rm --pull=always \
+    -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
     -v /var/run/docker.sock:/var/run/docker.sock \
     -p 3000:3000 \
     --add-host host.docker.internal:host-gateway \
-    --name openhands-app-$(date +%Y%m%d%H%M%S) \
-    ghcr.io/all-hands-ai/openhands:0.11
+    --name openhands-app \
+    docker.all-hands.dev/all-hands-ai/openhands:0.11
 ```
 
 You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
 
-You'll need a model provider and API key. One option that works well: [Claude 3.5 Sonnet](https://www.anthropic.com/api), but you have [many options](https://docs.all-hands.dev/modules/usage/llms).
+You'll need a model provider and API key.
+[Anthropic's Claude 3.5 Sonnet (`anthropic/claude-3-5-sonnet-20241022`)](https://www.anthropic.com/api)
+works best, but you have [many options](https://docs.all-hands.dev/modules/usage/llms).
 
 ---
 
-You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
-or as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode).
+You can also [connect OpenHands to your local filesystem](https://docs.all-hands.dev/modules/usage/runtimes),
+run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
+or interact with it via a [friendly CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode).
 
 Visit [Installation](https://docs.all-hands.dev/modules/usage/installation) for more information and setup instructions.
 

+ 1 - 0
containers/app/Dockerfile

@@ -41,6 +41,7 @@ ENV SANDBOX_LOCAL_RUNTIME_URL=http://host.docker.internal
 ENV USE_HOST_NETWORK=false
 ENV WORKSPACE_BASE=/opt/workspace_base
 ENV OPENHANDS_BUILD_VERSION=$OPENHANDS_BUILD_VERSION
+ENV SANDBOX_USER_ID=0
 RUN mkdir -p $WORKSPACE_BASE
 
 RUN apt-get update -y \

+ 5 - 0
containers/app/entrypoint.sh

@@ -18,6 +18,11 @@ if [ -z "$SANDBOX_USER_ID" ]; then
   exit 1
 fi
 
+if [ -z "$WORKSPACE_MOUNT_PATH" ]; then
+  # This is set to /opt/workspace in the Dockerfile. But if the user isn't mounting, we want to unset it so that OpenHands doesn't mount at all
+  unset WORKSPACE_BASE
+fi
+
 if [[ "$SANDBOX_USER_ID" -eq 0 ]]; then
   echo "Running OpenHands as root"
   export RUN_AS_OPENHANDS=false

+ 1 - 1
docs/modules/usage/how-to/cli-mode.md

@@ -57,7 +57,7 @@ docker run -it \
     -v /var/run/docker.sock:/var/run/docker.sock \
     --add-host host.docker.internal:host-gateway \
     --name openhands-app-$(date +%Y%m%d%H%M%S) \
-    ghcr.io/all-hands-ai/openhands:0.11 \
+    docker.all-hands.dev/all-hands-ai/openhands:0.11 \
     python -m openhands.core.cli
 ```
 

+ 1 - 1
docs/modules/usage/how-to/headless-mode.md

@@ -51,6 +51,6 @@ docker run -it \
     -v /var/run/docker.sock:/var/run/docker.sock \
     --add-host host.docker.internal:host-gateway \
     --name openhands-app-$(date +%Y%m%d%H%M%S) \
-    ghcr.io/all-hands-ai/openhands:0.11 \
+    docker.all-hands.dev/all-hands-ai/openhands:0.11 \
     python -m openhands.core.main -t "write a bash script that prints hi"
 ```

+ 6 - 6
docs/modules/usage/how-to/openshift-example.md

@@ -150,7 +150,7 @@ metadata:
 spec:
   containers:
   - name: openhands-app-2024
-    image: ghcr.io/all-hands-ai/openhands:main
+    image: docker.all-hands.dev/all-hands-ai/openhands:main
     env:
     - name: SANDBOX_USER_ID
       value: "1000"
@@ -164,7 +164,7 @@ spec:
     ports:
     - containerPort: 3000
   - name: openhands-sandbox-2024
-    image: ghcr.io/all-hands-ai/sandbox:main
+    image: docker.all-hands.dev/all-hands-ai/runtime:main
     ports:
     - containerPort: 51963
     command: ["/usr/sbin/sshd", "-D", "-p 51963", "-o", "PermitRootLogin=yes"]
@@ -205,10 +205,10 @@ LAST SEEN   TYPE     REASON                   OBJECT
 9s          Normal   SuccessfulAttachVolume   pod/openhands-app-2024                AttachVolume.Attach succeeded for volume "pvc-2b1d223a-1c8f-4990-8e3d-68061a9ae252"
 9s          Normal   SuccessfulAttachVolume   pod/openhands-app-2024                AttachVolume.Attach succeeded for volume "pvc-31f15b25-faad-4665-a25f-201a530379af"
 6s          Normal   AddedInterface           pod/openhands-app-2024                Add eth0 [10.128.2.48/23] from openshift-sdn
-6s          Normal   Pulled                   pod/openhands-app-2024                Container image "ghcr.io/all-hands-ai/openhands:main" already present on machine
+6s          Normal   Pulled                   pod/openhands-app-2024                Container image "docker.all-hands.dev/all-hands-ai/openhands:main" already present on machine
 6s          Normal   Created                  pod/openhands-app-2024                Created container openhands-app-2024
 6s          Normal   Started                  pod/openhands-app-2024                Started container openhands-app-2024
-6s          Normal   Pulled                   pod/openhands-app-2024                Container image "ghcr.io/all-hands-ai/sandbox:main" already present on machine
+6s          Normal   Pulled                   pod/openhands-app-2024                Container image "docker.all-hands.dev/all-hands-ai/sandbox:main" already present on machine
 5s          Normal   Created                  pod/openhands-app-2024                Created container openhands-sandbox-2024
 5s          Normal   Started                  pod/openhands-app-2024                Started container openhands-sandbox-2024
 83s         Normal   WaitForFirstConsumer     persistentvolumeclaim/workspace-pvc   waiting for first consumer to be created before binding
@@ -334,7 +334,7 @@ spec:
     spec:
       containers:
       - name: openhands-app-2024
-        image: ghcr.io/all-hands-ai/openhands:main
+        image: docker.all-hands.dev/all-hands-ai/openhands:main
         env:
         - name: SANDBOX_USER_ID
           value: "1000"
@@ -356,7 +356,7 @@ spec:
         ports:
         - containerPort: 3000
       - name: openhands-sandbox-2024
-        image: ghcr.io/opendevin/sandbox:main
+        image: docker.all-hands.dev/all-hands-ai/runtime:main
     #    securityContext:
     #      privileged: true  # Add this to allow privileged access
         ports:

+ 8 - 17
docs/modules/usage/installation.mdx

@@ -8,24 +8,18 @@
 
 ## Start the app
 
-The easiest way to run OpenHands is in Docker. You can change `WORKSPACE_BASE` below to point OpenHands to
-existing code that you'd like to modify.
+The easiest way to run OpenHands is in Docker.
 
 ```bash
-export WORKSPACE_BASE=$(pwd)/workspace
+docker pull docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik
 
-docker pull ghcr.io/all-hands-ai/runtime:0.11-nikolaik
-
-docker run -it --pull=always \
-    -e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.11-nikolaik \
-    -e SANDBOX_USER_ID=$(id -u) \
-    -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-    -v $WORKSPACE_BASE:/opt/workspace_base \
+docker run -it --rm --pull=always \
+    -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
     -v /var/run/docker.sock:/var/run/docker.sock \
     -p 3000:3000 \
     --add-host host.docker.internal:host-gateway \
-    --name openhands-app-$(date +%Y%m%d%H%M%S) \
-    ghcr.io/all-hands-ai/openhands:0.11
+    --name openhands-app \
+    docker.all-hands.dev/all-hands-ai/openhands:0.11
 ```
 
 You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode), or using the [OpenHands GitHub Action](https://docs.all-hands.dev/modules/usage/how-to/github-action).
@@ -34,9 +28,6 @@ You can also run OpenHands in a scriptable [headless mode](https://docs.all-hand
 
 After running the command above, you'll find OpenHands running at [http://localhost:3000](http://localhost:3000).
 
-The agent will have access to the `./workspace` folder to do its work. You can copy existing code here, or change `WORKSPACE_BASE` in the
-command to point to an existing folder.
-
 Upon launching OpenHands, you'll see a settings modal. You **must** select an `LLM Provider` and `LLM Model` and enter a corresponding `API Key`.
 These can be changed at any time by selecting the `Settings` button (gear icon) in the UI.
 
@@ -52,9 +43,9 @@ The `Advanced Options` also allow you to specify a `Base URL` if required.
 ## Versions
 
 The command above pulls the most recent stable release of OpenHands. You have other options as well:
-- For a specific release, use `ghcr.io/all-hands-ai/openhands:$VERSION`, replacing $VERSION with the version number.
+- For a specific release, use `docker.all-hands.dev/all-hands-ai/openhands:$VERSION`, replacing $VERSION with the version number.
 - We use semver, and release major, minor, and patch tags. So `0.9` will automatically point to the latest `0.9.x` release, and `0` will point to the latest `0.x.x` release.
-- For the most up-to-date development version, you can use `ghcr.io/all-hands-ai/openhands:main`. This version is unstable and is recommended for testing or development purposes only.
+- For the most up-to-date development version, you can use `docker.all-hands.dev/all-hands-ai/openhands:main`. This version is unstable and is recommended for testing or development purposes only.
 
 You can choose the tag that best suits your needs based on stability requirements and desired features.
 

+ 7 - 31
docs/modules/usage/llms/local-llms.md

@@ -35,32 +35,15 @@ Use the instructions [here](../getting-started) to start OpenHands using Docker.
 But when running `docker run`, you'll need to add a few more arguments:
 
 ```bash
---add-host host.docker.internal:host-gateway \
--e LLM_OLLAMA_BASE_URL="http://host.docker.internal:11434" \
-```
-
-LLM_OLLAMA_BASE_URL is optional. If you set it, it will be used to show the available installed models in the UI.
-
-Example:
-
-```bash
-# The directory you want OpenHands to modify. MUST be an absolute path!
-export WORKSPACE_BASE=$(pwd)/workspace
-
-docker run \
-    -it \
-    --pull=always \
+docker run # ...
     --add-host host.docker.internal:host-gateway \
-    -e SANDBOX_USER_ID=$(id -u) \
     -e LLM_OLLAMA_BASE_URL="http://host.docker.internal:11434" \
-    -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-    -v $WORKSPACE_BASE:/opt/workspace_base \
-    -v /var/run/docker.sock:/var/run/docker.sock \
-    -p 3000:3000 \
-    ghcr.io/all-hands-ai/openhands:main
+    # ...
 ```
 
-You should now be able to connect to `http://localhost:3000/`
+LLM_OLLAMA_BASE_URL is optional. If you set it, it will be used to show
+the available installed models in the UI.
+
 
 ### Configure the Web Application
 
@@ -176,18 +159,11 @@ CUSTOM_LLM_PROVIDER="openai"
 ### Docker
 
 ```bash
-docker run \
-    -it \
-    --pull=always \
-    -e SANDBOX_USER_ID=$(id -u) \
+docker run # ...
     -e LLM_MODEL="openai/lmstudio" \
     -e LLM_BASE_URL="http://host.docker.internal:1234/v1" \
     -e CUSTOM_LLM_PROVIDER="openai" \
-    -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
-    -v $WORKSPACE_BASE:/opt/workspace_base \
-    -v /var/run/docker.sock:/var/run/docker.sock \
-    -p 3000:3000 \
-    ghcr.io/all-hands-ai/openhands:main
+    # ...
 ```
 
 You should now be able to connect to `http://localhost:3000/`

+ 79 - 0
docs/modules/usage/runtimes.md

@@ -0,0 +1,79 @@
+# Runtime Configuration
+
+A Runtime is an environment where the OpenHands agent can edit files and run
+commands.
+
+By default, OpenHands uses a Docker-based runtime, running on your local computer.
+This means you only have to pay for the LLM you're using, and your code is only ever sent to the LLM.
+
+We also support "remote" runtimes, which are typically managed by third-parties.
+They can make setup a bit simpler and more scalable, especially
+if you're running many OpenHands conversations in parallel (e.g. to do evaluation).
+
+## Docker Runtime
+This is the default Runtime that's used when you start OpenHands. You might notice
+some flags being passed to `docker run` that make this possible:
+
+```
+docker run # ...
+    -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
+    -v /var/run/docker.sock:/var/run/docker.sock \
+    # ...
+```
+
+The `SANDBOX_RUNTIME_CONTAINER_IMAGE` from nikolaik is a pre-built runtime image
+that contains our Runtime server, as well as some basic utilities for Python and NodeJS.
+You can also [build your own runtime image](how-to/custom-sandbox-guide).
+
+### Connecting to Your filesystem
+One useful feature here is the ability to connect to your local filesystem.
+
+To mount your filesystem into the runtime, add the following options to
+the `docker run` command:
+
+```bash
+export WORKSPACE_BASE=/path/to/your/code
+
+docker run # ...
+    -e SANDBOX_USER_ID=$(id -u) \
+    -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
+    -v $WORKSPACE_BASE:/opt/workspace_base \
+    # ...
+```
+
+Be careful! There's nothing stopping the OpenHands agent from deleting or modifying
+any files that are mounted into its workspace.
+
+This setup can cause some issues with file permissions (hence the `SANDBOX_USER_ID` variable)
+but seems to work well on most systems.
+
+## All Hands Runtime
+The All Hands Runtime is currently in beta. You can request access by joining
+the #remote-runtime-limited-beta channel on Slack (see the README for an invite).
+
+To use the All Hands Runtime, set the following environment variables when
+starting OpenHands:
+
+```bash
+docker run # ...
+    -e RUNTIME=remote \
+    -e SANDBOX_REMOTE_RUNTIME_API_URL="https://runtime.app.all-hands.dev" \
+    -e SANDBOX_API_KEY="your-all-hands-api-key" \
+    -e SANDBOX_KEEP_REMOTE_RUNTIME_ALIVE="true" \
+    -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
+    # ...
+```
+
+## Modal Runtime
+Our partners at [Modal](https://modal.com/) have also provided a runtime for OpenHands.
+
+To use the Modal Runtime, create an account, and then [create an API key](https://modal.com/settings)
+
+You'll then need to set the following environment variables when starting OpenHands:
+```bash
+docker run # ...
+    -e RUNTIME=modal \
+    -e MODAL_API_TOKEN_ID="your-id" \
+    -e MODAL_API_TOKEN_SECRET="your-secret" \
+    -e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
+```

+ 5 - 0
docs/sidebars.ts

@@ -90,6 +90,11 @@ const sidebars: SidebarsConfig = {
             },
           ],
         },
+        {
+          type: 'doc',
+          label: 'Runtime Configuration',
+          id: 'usage/runtimes',
+        },
         {
           type: 'doc',
           label: 'Custom Sandbox',

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 788 - 655
docs/yarn.lock


+ 0 - 2
openhands/core/config/__init__.py

@@ -3,7 +3,6 @@ from openhands.core.config.app_config import AppConfig
 from openhands.core.config.config_utils import (
     OH_DEFAULT_AGENT,
     OH_MAX_ITERATIONS,
-    UndefinedString,
     get_field_info,
 )
 from openhands.core.config.llm_config import LLMConfig
@@ -22,7 +21,6 @@ from openhands.core.config.utils import (
 __all__ = [
     'OH_DEFAULT_AGENT',
     'OH_MAX_ITERATIONS',
-    'UndefinedString',
     'AgentConfig',
     'AppConfig',
     'LLMConfig',

+ 2 - 7
openhands/core/config/app_config.py

@@ -1,4 +1,3 @@
-import os
 import uuid
 from dataclasses import dataclass, field, fields, is_dataclass
 from typing import ClassVar
@@ -8,7 +7,6 @@ from openhands.core.config.agent_config import AgentConfig
 from openhands.core.config.config_utils import (
     OH_DEFAULT_AGENT,
     OH_MAX_ITERATIONS,
-    UndefinedString,
     get_field_info,
 )
 from openhands.core.config.llm_config import LLMConfig
@@ -55,11 +53,8 @@ class AppConfig:
     file_store: str = 'memory'
     file_store_path: str = '/tmp/file_store'
     trajectories_path: str | None = None
-    # TODO: clean up workspace path after the removal of ServerRuntime
-    workspace_base: str = os.path.join(os.getcwd(), 'workspace')
-    workspace_mount_path: str | None = (
-        UndefinedString.UNDEFINED  # this path should always be set when config is fully loaded
-    )  # when set to None, do not mount the workspace
+    workspace_base: str | None = None
+    workspace_mount_path: str | None = None
     workspace_mount_path_in_sandbox: str = '/workspace'
     workspace_mount_rewrite: str | None = None
     cache_dir: str = '/tmp/cache'

+ 0 - 5
openhands/core/config/config_utils.py

@@ -1,4 +1,3 @@
-from enum import Enum
 from types import UnionType
 from typing import get_args, get_origin
 
@@ -6,10 +5,6 @@ OH_DEFAULT_AGENT = 'CodeActAgent'
 OH_MAX_ITERATIONS = 100
 
 
-class UndefinedString(str, Enum):
-    UNDEFINED = 'UNDEFINED'
-
-
 def get_field_info(f):
     """Extract information about a dataclass field: type, optional, and default.
 

+ 9 - 11
openhands/core/config/utils.py

@@ -15,7 +15,6 @@ from openhands.core.config.app_config import AppConfig
 from openhands.core.config.config_utils import (
     OH_DEFAULT_AGENT,
     OH_MAX_ITERATIONS,
-    UndefinedString,
 )
 from openhands.core.config.llm_config import LLMConfig
 from openhands.core.config.sandbox_config import SandboxConfig
@@ -191,16 +190,15 @@ def load_from_toml(cfg: AppConfig, toml_file: str = 'config.toml'):
 
 def finalize_config(cfg: AppConfig):
     """More tweaks to the config after it's been loaded."""
-    cfg.workspace_base = os.path.abspath(cfg.workspace_base)
-    # Set workspace_mount_path if not set by the user
-    if cfg.workspace_mount_path is UndefinedString.UNDEFINED:
-        cfg.workspace_mount_path = cfg.workspace_base
-
-    if cfg.workspace_mount_rewrite:  # and not config.workspace_mount_path:
-        # TODO why do we need to check if workspace_mount_path is None?
-        base = cfg.workspace_base or os.getcwd()
-        parts = cfg.workspace_mount_rewrite.split(':')
-        cfg.workspace_mount_path = base.replace(parts[0], parts[1])
+    if cfg.workspace_base is not None:
+        cfg.workspace_base = os.path.abspath(cfg.workspace_base)
+        if cfg.workspace_mount_path is None:
+            cfg.workspace_mount_path = cfg.workspace_base
+
+        if cfg.workspace_mount_rewrite:
+            base = cfg.workspace_base or os.getcwd()
+            parts = cfg.workspace_mount_rewrite.split(':')
+            cfg.workspace_mount_path = base.replace(parts[0], parts[1])
 
     for llm in cfg.llms.values():
         if llm.embedding_base_url is None:

+ 22 - 21
openhands/runtime/impl/eventstream/eventstream_runtime.py

@@ -205,11 +205,7 @@ class EventStreamRuntime(Runtime):
             self.log(
                 'info', f'Starting runtime with image: {self.runtime_container_image}'
             )
-            self._init_container(
-                sandbox_workspace_dir=self.config.workspace_mount_path_in_sandbox,  # e.g. /workspace
-                mount_dir=self.config.workspace_mount_path,  # e.g. /opt/openhands/_test_workspace
-                plugins=self.plugins,
-            )
+            self._init_container()
             self.log('info', f'Container started: {self.container_name}')
 
         else:
@@ -246,19 +242,14 @@ class EventStreamRuntime(Runtime):
         stop=tenacity.stop_after_attempt(5) | stop_if_should_exit(),
         wait=tenacity.wait_exponential(multiplier=1, min=4, max=60),
     )
-    def _init_container(
-        self,
-        sandbox_workspace_dir: str,
-        mount_dir: str | None = None,
-        plugins: list[PluginRequirement] | None = None,
-    ):
+    def _init_container(self):
         try:
             self.log('debug', 'Preparing to start container...')
             self.send_status_message('STATUS$PREPARING_CONTAINER')
             plugin_arg = ''
-            if plugins is not None and len(plugins) > 0:
+            if self.plugins is not None and len(self.plugins) > 0:
                 plugin_arg = (
-                    f'--plugins {" ".join([plugin.name for plugin in plugins])} '
+                    f'--plugins {" ".join([plugin.name for plugin in self.plugins])} '
                 )
 
             self._host_port = self._find_available_port()
@@ -294,17 +285,27 @@ class EventStreamRuntime(Runtime):
                 environment['DEBUG'] = 'true'
 
             self.log('debug', f'Workspace Base: {self.config.workspace_base}')
-            if mount_dir is not None and sandbox_workspace_dir is not None:
+            if (
+                self.config.workspace_mount_path is not None
+                and self.config.workspace_mount_path_in_sandbox is not None
+            ):
                 # e.g. result would be: {"/home/user/openhands/workspace": {'bind': "/workspace", 'mode': 'rw'}}
-                volumes = {mount_dir: {'bind': sandbox_workspace_dir, 'mode': 'rw'}}
-                self.log('debug', f'Mount dir: {mount_dir}')
+                volumes = {
+                    self.config.workspace_mount_path: {
+                        'bind': self.config.workspace_mount_path_in_sandbox,
+                        'mode': 'rw',
+                    }
+                }
+                logger.debug(f'Mount dir: {self.config.workspace_mount_path}')
             else:
-                self.log(
-                    'warn',
-                    'Warning: Mount dir is not set, will not mount the workspace directory to the container!\n',
+                logger.debug(
+                    'Mount dir is not set, will not mount the workspace directory to the container'
                 )
                 volumes = None
-            self.log('debug', f'Sandbox workspace: {sandbox_workspace_dir}')
+            self.log(
+                'debug',
+                f'Sandbox workspace: {self.config.workspace_mount_path_in_sandbox}'
+            )
 
             if self.config.sandbox.browsergym_eval_env is not None:
                 browsergym_arg = (
@@ -319,7 +320,7 @@ class EventStreamRuntime(Runtime):
                     f'/openhands/micromamba/bin/micromamba run -n openhands '
                     f'poetry run '
                     f'python -u -m openhands.runtime.action_execution_server {self._container_port} '
-                    f'--working-dir "{sandbox_workspace_dir}" '
+                    f'--working-dir "{self.config.workspace_mount_path_in_sandbox}" '
                     f'{plugin_arg}'
                     f'--username {"openhands" if self.config.run_as_openhands else "root"} '
                     f'--user-id {self.config.sandbox.user_id} '

+ 15 - 33
tests/unit/test_config.py

@@ -6,7 +6,6 @@ from openhands.core.config import (
     AgentConfig,
     AppConfig,
     LLMConfig,
-    UndefinedString,
     finalize_config,
     get_llm_config_arg,
     load_from_env,
@@ -82,12 +81,8 @@ def test_load_from_old_style_env(monkeypatch, default_config):
     assert default_config.get_agent_config().memory_enabled is True
     assert default_config.default_agent == 'PlannerAgent'
     assert default_config.workspace_base == '/opt/files/workspace'
-    assert (
-        default_config.workspace_mount_path is UndefinedString.UNDEFINED
-    )  # before finalize_config
-    assert (
-        default_config.workspace_mount_path_in_sandbox is not UndefinedString.UNDEFINED
-    )
+    assert default_config.workspace_mount_path is None  # before finalize_config
+    assert default_config.workspace_mount_path_in_sandbox is not None
     assert default_config.sandbox.base_container_image == 'custom_image'
 
 
@@ -148,11 +143,8 @@ default_agent = "TestAgent"
     assert default_config.workspace_base == '/opt/files2/workspace'
     assert default_config.sandbox.timeout == 1
 
-    # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
-    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
-    assert (
-        default_config.workspace_mount_path_in_sandbox is not UndefinedString.UNDEFINED
-    )
+    assert default_config.workspace_mount_path is None
+    assert default_config.workspace_mount_path_in_sandbox is not None
     assert default_config.workspace_mount_path_in_sandbox == '/workspace'
 
     finalize_config(default_config)
@@ -231,8 +223,7 @@ sandbox_user_id = 1001
 
     load_from_toml(default_config, temp_toml_file)
 
-    # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
-    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
+    assert default_config.workspace_mount_path is None
 
     load_from_env(default_config, os.environ)
 
@@ -244,11 +235,9 @@ sandbox_user_id = 1001
 
     # after we set workspace_base to 'UNDEFINED' in the environment,
     # workspace_base should be set to that
-    # workspace_mount path is still UndefinedString.UNDEFINED
-    assert default_config.workspace_base is not UndefinedString.UNDEFINED
+    assert default_config.workspace_base is not None
     assert default_config.workspace_base == 'UNDEFINED'
-    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
-    assert default_config.workspace_mount_path == 'UNDEFINED'
+    assert default_config.workspace_mount_path is None
 
     assert default_config.disable_color is True
     assert default_config.sandbox.timeout == 1000
@@ -284,8 +273,7 @@ user_id = 1001
 
     load_from_toml(default_config, temp_toml_file)
 
-    # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
-    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
+    assert default_config.workspace_mount_path is None
 
     # before load_from_env, values are set to the values from the toml file
     assert default_config.get_llm_config().api_key == 'toml-api-key'
@@ -338,9 +326,7 @@ user_id = 1001
 def test_defaults_dict_after_updates(default_config):
     # Test that `defaults_dict` retains initial values after updates.
     initial_defaults = default_config.defaults_dict
-    assert (
-        initial_defaults['workspace_mount_path']['default'] is UndefinedString.UNDEFINED
-    )
+    assert initial_defaults['workspace_mount_path']['default'] is None
     assert initial_defaults['default_agent']['default'] == 'CodeActAgent'
 
     updated_config = AppConfig()
@@ -352,10 +338,7 @@ def test_defaults_dict_after_updates(default_config):
 
     defaults_after_updates = updated_config.defaults_dict
     assert defaults_after_updates['default_agent']['default'] == 'CodeActAgent'
-    assert (
-        defaults_after_updates['workspace_mount_path']['default']
-        is UndefinedString.UNDEFINED
-    )
+    assert defaults_after_updates['workspace_mount_path']['default'] is None
     assert defaults_after_updates['sandbox']['timeout']['default'] == 120
     assert (
         defaults_after_updates['sandbox']['base_container_image']['default']
@@ -384,17 +367,16 @@ def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config):
 
 def test_finalize_config(default_config):
     # Test finalize config
-    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
+    assert default_config.workspace_mount_path is None
+    default_config.workspace_base = None
     finalize_config(default_config)
 
-    assert default_config.workspace_mount_path == os.path.abspath(
-        default_config.workspace_base
-    )
+    assert default_config.workspace_mount_path is None
 
 
-# tests for workspace, mount path, path in sandbox, cache dir
 def test_workspace_mount_path_default(default_config):
-    assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
+    assert default_config.workspace_mount_path is None
+    default_config.workspace_base = '/home/user/project'
     finalize_config(default_config)
     assert default_config.workspace_mount_path == os.path.abspath(
         default_config.workspace_base

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно