From 7e237be43d3c38db115f6b219d89213fca8eb92a Mon Sep 17 00:00:00 2001 From: Tim O'Farrell Date: Tue, 21 Oct 2025 12:14:25 -0600 Subject: [PATCH 1/4] Fixed persisence and added bash command which waits for execution --- .../openhands/agent_server/bash_router.py | 16 +++++++++++++--- .../openhands/sdk/conversation/state.py | 4 +++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/openhands-agent-server/openhands/agent_server/bash_router.py b/openhands-agent-server/openhands/agent_server/bash_router.py index b5f5ec9fc9..b067a78940 100644 --- a/openhands-agent-server/openhands/agent_server/bash_router.py +++ b/openhands-agent-server/openhands/agent_server/bash_router.py @@ -2,7 +2,7 @@ import logging from datetime import datetime -from typing import Annotated, Literal +from typing import Annotated, Literal, cast from uuid import UUID from fastapi import ( @@ -18,6 +18,7 @@ BashEventBase, BashEventPage, BashEventSortOrder, + BashOutput, ExecuteBashRequest, ) @@ -80,13 +81,22 @@ async def batch_get_bash_events( return events -@bash_router.post("/execute_bash_command") +@bash_router.post("/start_bash_command") async def start_bash_command(request: ExecuteBashRequest) -> BashCommand: - """Execute a bash command""" + """Execute a bash command in the background""" command, _ = await bash_event_service.start_bash_command(request) return command +@bash_router.post("/execute_bash_command") +async def execute_bash_command(request: ExecuteBashRequest) -> BashOutput: + """Execute a bash command and wait for a result""" + command, task = await bash_event_service.start_bash_command(request) + page = await bash_event_service.search_bash_events(command_id__eq=command.id) + result = cast(BashOutput, page.items[-1]) + return result + + @bash_router.delete("/bash_events") async def clear_all_bash_events() -> dict[str, int]: """Clear all bash events from storage""" diff --git a/openhands-sdk/openhands/sdk/conversation/state.py b/openhands-sdk/openhands/sdk/conversation/state.py index 06c4cf0e39..384cc9896f 100644 --- a/openhands-sdk/openhands/sdk/conversation/state.py +++ b/openhands-sdk/openhands/sdk/conversation/state.py @@ -133,7 +133,9 @@ def _save_base_state(self, fs: FileStore) -> None: """ Persist base state snapshot (no events; events are file-backed). """ - payload = self.model_dump_json(exclude_none=True) + payload = self.model_dump_json( + exclude_none=True, context={"expose_secrets": True} + ) fs.write(BASE_STATE, payload) # ===== Factory: open-or-create (no load/save methods needed) ===== From 8c9a033dbc54ddcaa9092946874a47103ea41709 Mon Sep 17 00:00:00 2001 From: Tim O'Farrell Date: Tue, 21 Oct 2025 12:22:35 -0600 Subject: [PATCH 2/4] Saving secrets --- .../openhands/agent_server/event_service.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openhands-agent-server/openhands/agent_server/event_service.py b/openhands-agent-server/openhands/agent_server/event_service.py index 60cc92610e..c275c76f81 100644 --- a/openhands-agent-server/openhands/agent_server/event_service.py +++ b/openhands-agent-server/openhands/agent_server/event_service.py @@ -49,7 +49,9 @@ async def load_meta(self): async def save_meta(self): self.stored.updated_at = utc_now() meta_file = self.conversation_dir / "meta.json" - meta_file.write_text(self.stored.model_dump_json()) + meta_file.write_text( + self.stored.model_dump_json(context={"expose_secrets": True}) + ) async def get_event(self, event_id: str) -> Event | None: if not self._conversation: From 59884106d25acba5f11139a95dbc9d3a65b408cb Mon Sep 17 00:00:00 2001 From: Tim O'Farrell Date: Tue, 21 Oct 2025 14:35:04 -0600 Subject: [PATCH 3/4] WIP --- .../openhands/agent_server/cmd_router.py | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 openhands-agent-server/openhands/agent_server/cmd_router.py diff --git a/openhands-agent-server/openhands/agent_server/cmd_router.py b/openhands-agent-server/openhands/agent_server/cmd_router.py deleted file mode 100644 index 65f9f53629..0000000000 --- a/openhands-agent-server/openhands/agent_server/cmd_router.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Command router for OpenHands SDK.""" - -import logging -from uuid import UUID - -from fastapi import APIRouter - - -cmd_router = APIRouter(prefix="/cmd", tags=["Command"]) -logger = logging.getLogger(__name__) - - -@cmd_router.get("/cmd/git-changes/{conversation_id}") -async def git_changes( - conversation_id: UUID, -) -> str: - raise NotImplementedError() - - -# bash event routes -@cmd_router.get("/cmd/git-diff/{conversation_id}") -async def git_diff( - conversation_id: UUID, -) -> str: - raise NotImplementedError() - - -@cmd_router.get("/cmd/download-trajectory/{conversation_id}") -async def download_trajectory( - conversation_id: UUID, -) -> str: - raise NotImplementedError() From 31a37b7baa7157c5967c762cfa35ec8c1ffe3a91 Mon Sep 17 00:00:00 2001 From: Tim O'Farrell Date: Tue, 21 Oct 2025 14:59:27 -0600 Subject: [PATCH 4/4] Revert --- openhands-sdk/openhands/sdk/conversation/state.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openhands-sdk/openhands/sdk/conversation/state.py b/openhands-sdk/openhands/sdk/conversation/state.py index 384cc9896f..06c4cf0e39 100644 --- a/openhands-sdk/openhands/sdk/conversation/state.py +++ b/openhands-sdk/openhands/sdk/conversation/state.py @@ -133,9 +133,7 @@ def _save_base_state(self, fs: FileStore) -> None: """ Persist base state snapshot (no events; events are file-backed). """ - payload = self.model_dump_json( - exclude_none=True, context={"expose_secrets": True} - ) + payload = self.model_dump_json(exclude_none=True) fs.write(BASE_STATE, payload) # ===== Factory: open-or-create (no load/save methods needed) =====