diff --git a/.github/workflows/dummy-agent-test.yml b/.github/workflows/dummy-agent-test.yml index aae26467955f..392876db22bd 100644 --- a/.github/workflows/dummy-agent-test.yml +++ b/.github/workflows/dummy-agent-test.yml @@ -10,6 +10,9 @@ on: - main pull_request: +env: + PERSIST_SANDBOX : "false" + jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml index ab5604e9d326..3f0f80e3e580 100644 --- a/.github/workflows/run-integration-tests.yml +++ b/.github/workflows/run-integration-tests.yml @@ -15,6 +15,9 @@ on: - 'evaluation/**' pull_request: +env: + PERSIST_SANDBOX : "false" + jobs: integration-tests-on-linux: name: Integration Tests on Linux diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index d8fb16b13ae6..f6df12579f0a 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -15,6 +15,9 @@ on: - 'evaluation/**' pull_request: +env: + PERSIST_SANDBOX : "false" + jobs: test-on-macos: name: Test on macOS diff --git a/README.md b/README.md index e1f5256b3c82..b9b8a3597509 100644 --- a/README.md +++ b/README.md @@ -54,10 +54,11 @@ To start the app, run these commands, replacing `$(pwd)/workspace` with the dire ```bash # The directory you want OpenDevin to work with. MUST be an absolute path! export WORKSPACE_BASE=$(pwd)/workspace; +export SSH_PASSWORD="set some long password here"; ``` -> [!WARNING] -> OpenDevin runs bash commands within a Docker sandbox, so it should not affect your machine. +> [!WARNING] +> OpenDevin runs bash commands within a Docker sandbox, so it should not affect your machine. > But your workspace directory will be attached to that sandbox, and files in the directory may be modified or deleted. ```bash @@ -65,6 +66,7 @@ docker run \ -it \ --pull=always \ -e SANDBOX_USER_ID=$(id -u) \ + -e SSH_PASSWORD=$SSH_PASSWORD \ -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \ -v $WORKSPACE_BASE:/opt/workspace_base \ -v /var/run/docker.sock:/var/run/docker.sock \ diff --git a/agenthub/codeact_agent/codeact_agent.py b/agenthub/codeact_agent/codeact_agent.py index 8bbc9fb72e2d..47cdc90ff651 100644 --- a/agenthub/codeact_agent/codeact_agent.py +++ b/agenthub/codeact_agent/codeact_agent.py @@ -196,7 +196,7 @@ def step(self, state: State) -> Action: {'role': 'system', 'content': self.system_message}, { 'role': 'user', - 'content': f"Here is an example of how you can interact with the environment for task solving:\n{EXAMPLES}\n\nNOW, LET'S START!", + 'content': f"Here is an example of how you can interact with the environment for task solving:\n{EXAMPLES}\n\nNOW, LET'S START!\n", }, ] diff --git a/docs/modules/usage/intro.mdx b/docs/modules/usage/intro.mdx index a6067a6e3eda..954387c8c63e 100644 --- a/docs/modules/usage/intro.mdx +++ b/docs/modules/usage/intro.mdx @@ -66,6 +66,7 @@ To start the app, run these commands, replacing `$(pwd)/workspace` with the dire ``` # The directory you want OpenDevin to work with. It MUST be an absolute path! export WORKSPACE_BASE=$(pwd)/workspace +export SSH_PASSWORD="set some long password here"; ``` :::warning @@ -78,6 +79,7 @@ docker run \ --pull=always \ -e LLM_API_KEY \ -e SANDBOX_USER_ID=$(id -u) \ + -e SSH_PASSWORD=$SSH_PASSWORD \ -e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \ -v $WORKSPACE_BASE:/opt/workspace_base \ -v /var/run/docker.sock:/var/run/docker.sock \ diff --git a/opendevin/core/config.py b/opendevin/core/config.py index 1a7e899788ad..b46003d99cae 100644 --- a/opendevin/core/config.py +++ b/opendevin/core/config.py @@ -179,6 +179,9 @@ class AppConfig(metaclass=Singleton): disable_color: bool = False sandbox_user_id: int = os.getuid() if hasattr(os, 'getuid') else 1000 sandbox_timeout: int = 120 + persist_sandbox: bool = True + ssh_port: int = 63710 + ssh_password: str | None = None github_token: str | None = None jwt_secret: str = uuid.uuid4().hex debug: bool = False diff --git a/opendevin/core/schema/config.py b/opendevin/core/schema/config.py index f409b9940a9e..4c66feeaf609 100644 --- a/opendevin/core/schema/config.py +++ b/opendevin/core/schema/config.py @@ -2,6 +2,7 @@ class ConfigType(str, Enum): + # For frontend LLM_CUSTOM_LLM_PROVIDER = 'LLM_CUSTOM_LLM_PROVIDER' LLM_MAX_INPUT_TOKENS = 'LLM_MAX_INPUT_TOKENS' LLM_MAX_OUTPUT_TOKENS = 'LLM_MAX_OUTPUT_TOKENS' diff --git a/opendevin/runtime/docker/ssh_box.py b/opendevin/runtime/docker/ssh_box.py index 02310b993b4b..2b11e312ba2a 100644 --- a/opendevin/runtime/docker/ssh_box.py +++ b/opendevin/runtime/docker/ssh_box.py @@ -216,38 +216,48 @@ def __init__( ) raise ex - self.instance_id = ( - sid + str(uuid.uuid4()) if sid is not None else str(uuid.uuid4()) - ) + if config.persist_sandbox: + self.instance_id = 'persisted' + else: + self.instance_id = (sid or '') + str(uuid.uuid4()) self.timeout = timeout - self.container_image = ( - config.sandbox_container_image - if container_image is None - else container_image - ) + self.container_image = container_image or config.sandbox_container_image self.container_name = self.container_name_prefix + self.instance_id # set up random user password - self._ssh_password = str(uuid.uuid4()) - self._ssh_port = find_available_tcp_port() - - # always restart the container, cuz the initial be regarded as a new session - n_tries = 5 - while n_tries > 0: - try: - self.restart_docker_container() - break - except Exception as e: - logger.exception( - 'Failed to start Docker container, retrying...', exc_info=False - ) - n_tries -= 1 - if n_tries == 0: - raise e - time.sleep(5) - self.setup_user() - + if config.persist_sandbox: + if not config.ssh_password: + raise Exception('Password must be set for persistent sandbox') + self._ssh_password = config.ssh_password + self._ssh_port = config.ssh_port + else: + self._ssh_password = str(uuid.uuid4()) + self._ssh_port = find_available_tcp_port() + try: + docker.DockerClient().containers.get(self.container_name) + is_initial_session = False + except docker.errors.NotFound: + is_initial_session = True + logger.info('Creating new Docker container') + if not config.persist_sandbox or is_initial_session: + n_tries = 5 + while n_tries > 0: + try: + self.restart_docker_container() + break + except Exception as e: + logger.exception( + 'Failed to start Docker container, retrying...', exc_info=False + ) + n_tries -= 1 + if n_tries == 0: + raise e + time.sleep(5) + self.setup_user() + else: + self.container = self.docker_client.containers.get(self.container_name) + logger.info('Using existing Docker container') try: self.start_ssh_session() except pxssh.ExceptionPxssh as e: @@ -391,6 +401,9 @@ def start_ssh_session(self): # cd to workspace self.ssh.sendline(f'cd {self.sandbox_workspace_dir}') self.ssh.prompt() + # load bashrc + self.ssh.sendline('source ~/.bashrc') + self.ssh.prompt() def get_exec_cmd(self, cmd: str) -> list[str]: if self.run_as_devin: @@ -704,7 +717,10 @@ def close(self): containers = self.docker_client.containers.list(all=True) for container in containers: try: - if container.name.startswith(self.container_name): + if ( + container.name.startswith(self.container_name) + and not config.persist_sandbox + ): # only remove the container we created # otherwise all other containers with the same prefix will be removed # which will mess up with parallel evaluation diff --git a/opendevin/runtime/plugins/jupyter/execute_server b/opendevin/runtime/plugins/jupyter/execute_server index cce59aaaa6c3..4560fa4a070a 100755 --- a/opendevin/runtime/plugins/jupyter/execute_server +++ b/opendevin/runtime/plugins/jupyter/execute_server @@ -134,7 +134,7 @@ class JupyterKernel: ) self.heartbeat_callback.start() - async def execute(self, code, timeout=60): + async def execute(self, code, timeout=120): if not self.ws: await self._connect()