Compare commits

...

24 Commits

Author SHA1 Message Date
chuckbutkus
7c556d6396 Merge branch 'main' into chuck-build 2025-09-23 14:25:16 -04:00
openhands
8bb5aa21b9 test 2025-09-23 14:19:20 -04:00
openhands
08096db29f test 2025-09-18 22:50:21 -04:00
openhands
b2b6ddf90c test 2025-09-18 22:24:35 -04:00
openhands
87fe36d811 test 2025-09-18 21:44:34 -04:00
openhands
39d255d313 test 2025-09-18 21:27:03 -04:00
openhands
e334b67f21 Add logging 2025-09-18 20:48:24 -04:00
chuckbutkus
d5c02bf87b Merge branch 'main' into allow-custom-user 2025-09-17 22:43:30 -04:00
openhands
14a4664fe8 Make su commands optional 2025-09-17 22:40:21 -04:00
chuckbutkus
3a7df33acf Merge branch 'main' into test-user 2025-09-17 14:02:52 -04:00
chuckbutkus
69fddecc7f Merge branch 'main' into test-user 2025-09-07 21:55:39 -04:00
Chuck Butkus
3afe5ccee5 Add Logging 2025-09-05 20:52:48 -04:00
chuckbutkus
3d5a8dcf5a Merge branch 'main' into test-user 2025-09-05 14:20:10 -04:00
Chuck Butkus
2ee1abe22c Lint fix 2025-09-05 13:16:03 -04:00
Chuck Butkus
148940f553 Added logging around alive checks 2025-09-05 11:10:57 -04:00
Chuck Butkus
1f09296136 Fix username checks 2025-09-03 21:40:13 -04:00
Chuck Butkus
70e5d12ba9 Revert "Change to a non-login shell"
This reverts commit bcb3160d95.
2025-08-29 01:48:47 -04:00
Chuck Butkus
bcb3160d95 Change to a non-login shell 2025-08-29 01:37:02 -04:00
Chuck Butkus
174c691744 Update 2025-08-28 02:25:05 -04:00
Chuck Butkus
af34d446e9 Remove vscode username restriction 2025-08-28 02:22:27 -04:00
Chuck Butkus
6604924f76 Fix bash username 2025-08-28 02:21:41 -04:00
chuckbutkus
b2def1e438 Merge branch 'main' into test-user 2025-08-27 23:33:45 -04:00
Chuck Butkus
2b8e47aca9 Add runtime user env vars 2025-08-27 23:02:39 -04:00
Chuck Butkus
dba8b28824 Logging 2025-08-27 21:30:47 -04:00
11 changed files with 100 additions and 15 deletions

1
force_build.txt Normal file
View File

@@ -0,0 +1 @@
test

View File

@@ -161,6 +161,7 @@ class EventStream(EventStore):
self._clean_up_subscriber(subscriber_id, callback_id)
def add_event(self, event: Event, source: EventSource) -> None:
logger.info(f'Adding event with ID {event.id}')
if event.id != Event.INVALID_ID:
raise ValueError(
f'Event already has an ID:{event.id}. It was probably added back to the EventStream from inside a handler, triggering a loop.'
@@ -183,6 +184,7 @@ class EventStream(EventStore):
if len(current_write_page) == self.cache_size:
self._write_page_cache = []
logger.info(f'Event now has ID {event.id}')
if event.id is not None:
# Write the event to the store - this can take some time
event_json = json.dumps(data)
@@ -204,12 +206,18 @@ class EventStream(EventStore):
def _store_cache_page(self, current_write_page: list[dict]):
"""Store a page in the cache. Reading individual events is slow when there are a lot of them, so we use pages."""
logger.info(
f'Writing event cache if page len {len(current_write_page)} is greater than {self.cache_size}'
)
if len(current_write_page) < self.cache_size:
return
start = current_write_page[0]['id']
end = start + self.cache_size
contents = json.dumps(current_write_page)
cache_filename = self._get_filename_for_cache(start, end)
logger.info(
f'writing event cache to {cache_filename} in file store of type {type(self.file_store)}'
)
self.file_store.write(cache_filename, contents)
def set_secrets(self, secrets: dict[str, str]) -> None:

View File

@@ -129,11 +129,15 @@ class ActionExecutionClient(Runtime):
return send_request(self.session, method, url, **kwargs)
def check_if_alive(self) -> None:
request_url = f'{self.action_execution_server_url}/alive'
self.log('debug', f'Sending request to: {request_url}')
response = self._send_action_server_request(
'GET',
f'{self.action_execution_server_url}/alive',
request_url,
timeout=5,
)
self.log('debug', f'Response status code: {response.status_code}')
self.log('debug', f'Response text: {response.text}')
assert response.is_closed
def list_files(self, path: str | None = None) -> list[str]:

View File

@@ -415,11 +415,19 @@ class RemoteRuntime(ActionExecutionClient):
def _wait_until_alive_impl(self) -> None:
self.log('debug', f'Waiting for runtime to be alive at url: {self.runtime_url}')
self.log(
'debug',
f'Sending request to: {self.config.sandbox.remote_runtime_api_url}/runtime/{self.runtime_id}',
)
runtime_info_response = self._send_runtime_api_request(
'GET',
f'{self.config.sandbox.remote_runtime_api_url}/runtime/{self.runtime_id}',
)
runtime_data = runtime_info_response.json()
self.log(
'debug',
f'received response: {runtime_data}',
)
assert 'runtime_id' in runtime_data
assert runtime_data['runtime_id'] == self.runtime_id
assert 'pod_status' in runtime_data

View File

@@ -13,6 +13,15 @@ from openhands.runtime.plugins.requirement import Plugin, PluginRequirement
from openhands.runtime.utils import find_available_tcp_port
from openhands.utils.shutdown_listener import should_continue
SU_TO_USER = os.getenv('SU_TO_USER', 'true').lower() in (
'1',
'true',
't',
'yes',
'y',
'on',
)
@dataclass
class JupyterRequirement(PluginRequirement):
@@ -36,7 +45,7 @@ class JupyterPlugin(Plugin):
if not is_local_runtime:
# Non-LocalRuntime
prefix = f'su - {username} -s '
prefix = f'su - {username} -s ' if SU_TO_USER else ''
# cd to code repo, setup all env vars and run micromamba
poetry_prefix = (
'cd /openhands/code\n'

View File

@@ -15,6 +15,16 @@ from openhands.runtime.plugins.requirement import Plugin, PluginRequirement
from openhands.runtime.utils.system import check_port_available
from openhands.utils.shutdown_listener import should_continue
RUNTIME_USERNAME = os.getenv('RUNTIME_USERNAME')
SU_TO_USER = os.getenv('SU_TO_USER', 'true').lower() in (
'1',
'true',
't',
'yes',
'y',
'on',
)
@dataclass
class VSCodeRequirement(PluginRequirement):
@@ -37,7 +47,7 @@ class VSCodePlugin(Plugin):
)
return
if username not in ['root', 'openhands']:
if username not in filter(None, [RUNTIME_USERNAME, 'root', 'openhands']):
self.vscode_port = None
self.vscode_connection_token = None
logger.warning(
@@ -83,13 +93,19 @@ class VSCodePlugin(Plugin):
if path_mode:
base_path_flag = f' --server-base-path /{runtime_id}/vscode'
cmd = (
f"su - {username} -s /bin/bash << 'EOF'\n"
f'sudo chown -R {username}:{username} /openhands/.openvscode-server\n'
f'cd {workspace_path}\n'
f'exec /openhands/.openvscode-server/bin/openvscode-server --host 0.0.0.0 --connection-token {self.vscode_connection_token} --port {self.vscode_port} --disable-workspace-trust{base_path_flag}\n'
'EOF'
)
cmd = (
(
f"su - {username} -s /bin/bash << 'EOF'\n"
if SU_TO_USER
else "/bin/bash << 'EOF'\n"
)
+ f'sudo chown -R {username}:{username} /openhands/.openvscode-server\n'
+ f'cd {workspace_path}\n'
+ 'exec /openhands/.openvscode-server/bin/openvscode-server '
+ f'--host 0.0.0.0 --connection-token {self.vscode_connection_token} '
+ f'--port {self.vscode_port} --disable-workspace-trust{base_path_flag}\n'
+ 'EOF'
)
# Using asyncio.create_subprocess_shell instead of subprocess.Popen
# to avoid ASYNC101 linting error

View File

@@ -20,6 +20,16 @@ from openhands.events.observation.commands import (
from openhands.runtime.utils.bash_constants import TIMEOUT_MESSAGE_TEMPLATE
from openhands.utils.shutdown_listener import should_continue
RUNTIME_USERNAME = os.getenv('RUNTIME_USERNAME')
SU_TO_USER = os.getenv('SU_TO_USER', 'true').lower() in (
'1',
'true',
't',
'yes',
'y',
'on',
)
def split_bash_commands(commands: str) -> list[str]:
if not commands.strip():
@@ -193,7 +203,9 @@ class BashSession:
def initialize(self) -> None:
self.server = libtmux.Server()
_shell_command = '/bin/bash'
if self.username in ['root', 'openhands']:
if SU_TO_USER and self.username in list(
filter(None, [RUNTIME_USERNAME, 'root', 'openhands'])
):
# This starts a non-login (new) shell for the given user
_shell_command = f'su {self.username} -'

View File

@@ -1,3 +1,5 @@
import os
from openhands.core.config import OpenHandsConfig
from openhands.core.logger import openhands_logger as logger
from openhands.runtime.plugins import PluginRequirement
@@ -12,6 +14,9 @@ DEFAULT_PYTHON_PREFIX = [
]
DEFAULT_MAIN_MODULE = 'openhands.runtime.action_execution_server'
RUNTIME_USERNAME = os.getenv('RUNTIME_USERNAME')
RUNTIME_UID = os.getenv('RUNTIME_UID')
def get_action_execution_server_startup_command(
server_port: int,
@@ -26,7 +31,10 @@ def get_action_execution_server_startup_command(
sandbox_config = app_config.sandbox
logger.debug(f'app_config {vars(app_config)}')
logger.debug(f'sandbox_config {vars(sandbox_config)}')
logger.debug(f'override_user_id {override_user_id}')
logger.debug(f'RUNTIME_USERNAME {RUNTIME_USERNAME}, RUNTIME_UID {RUNTIME_UID}')
logger.debug(
f'override_username {override_username}, override_user_id {override_user_id}'
)
# Plugin args
plugin_args = []
@@ -40,10 +48,15 @@ def get_action_execution_server_startup_command(
'--browsergym-eval-env'
] + sandbox_config.browsergym_eval_env.split(' ')
username = override_username or (
'openhands' if app_config.run_as_openhands else 'root'
username = (
override_username
or RUNTIME_USERNAME
or ('openhands' if app_config.run_as_openhands else 'root')
)
user_id = override_user_id or (1000 if app_config.run_as_openhands else 0)
user_id = (
override_user_id or RUNTIME_UID or (1000 if app_config.run_as_openhands else 0)
)
logger.debug(f'username {username}, user_id {user_id}')
base_cmd = [
*python_prefix,

View File

@@ -1,4 +1,5 @@
import threading
import traceback
from typing import Optional
import httpx
@@ -62,6 +63,11 @@ class BatchedWebHookFileStore(FileStore):
batch_size_limit_bytes: Size limit in bytes after which a batch is sent.
If None, uses the default constant WEBHOOK_BATCH_SIZE_LIMIT_BYTES.
"""
logger.info(
f'BatchedWebHookFileStore __init__ called with filestore type {type(file_store)}'
)
stack = '\n'.join(traceback.format_stack())
logger.info('BatchedWebHookFileStore __init__ stack trace:\n%s', stack)
self.file_store = file_store
self.base_url = base_url
if client is None:
@@ -89,6 +95,9 @@ class BatchedWebHookFileStore(FileStore):
path: The path to write to
contents: The contents to write
"""
logger.info(
f'BatchedWebHookFileStore write to {path} in filestore of type {type(self.file_store)}'
)
self.file_store.write(path, contents)
self._queue_update(path, 'write', contents)

View File

@@ -1,9 +1,11 @@
import os
import traceback
from typing import Any, TypedDict
import boto3
import botocore
from openhands.core.logger import openhands_logger as logger
from openhands.storage.files import FileStore
@@ -37,6 +39,8 @@ class S3FileStore(FileStore):
)
def write(self, path: str, contents: str | bytes) -> None:
stack = '\n'.join(traceback.format_stack())
logger.info('S3FileStore write stack trace:\n%s', stack)
try:
as_bytes = (
contents.encode('utf-8') if isinstance(contents, str) else contents

View File

@@ -27,6 +27,7 @@ class HttpSession:
headers = kwargs.get('headers') or {}
headers = {**self.headers, **headers}
kwargs['headers'] = headers
logger.debug(f'HttpSession:request called with args {args} and kwargs {kwargs}')
return CLIENT.request(*args, **kwargs)
def stream(self, *args, **kwargs):