From 7e498ebb12e659bcc8f638dea117ff2da97855b5 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 26 Sep 2025 20:39:46 +0000 Subject: [PATCH 01/11] feat: Add conversation ID support to OpenHands CLI - Add --id argument to CLI for specifying conversation ID - Modified simple_main.py to use argparse instead of typer for cleaner argument handling - Updated setup_conversation() to accept optional conversation_id parameter with UUID validation - Updated run_cli_entry() to pass conversation_id through the call chain - Fallback to random UUID generation when no ID provided or invalid UUID given - Maintains backward compatibility with existing usage Co-authored-by: openhands --- openhands-cli/openhands_cli/agent_chat.py | 9 +++++++-- openhands-cli/openhands_cli/setup.py | 21 +++++++++++++++++++-- openhands-cli/openhands_cli/simple_main.py | 16 +++++++++++++--- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/openhands-cli/openhands_cli/agent_chat.py b/openhands-cli/openhands_cli/agent_chat.py index 70d02b309dce..35ac52a88ed4 100644 --- a/openhands-cli/openhands_cli/agent_chat.py +++ b/openhands-cli/openhands_cli/agent_chat.py @@ -5,6 +5,8 @@ """ import sys +from typing import Optional + from openhands.sdk import ( Message, TextContent, @@ -38,9 +40,12 @@ def _restore_tty() -> None: pass -def run_cli_entry() -> None: +def run_cli_entry(conversation_id: Optional[str] = None) -> None: """Run the agent chat session using the agent SDK. + Args: + conversation_id: Optional conversation ID to use for the session. + Raises: AgentSetupError: If agent setup fails KeyboardInterrupt: If user interrupts the session @@ -52,7 +57,7 @@ def run_cli_entry() -> None: while not conversation: try: - conversation = setup_conversation() + conversation = setup_conversation(conversation_id) except MissingAgentSpec: settings_screen.handle_basic_settings(escapable=False) diff --git a/openhands-cli/openhands_cli/setup.py b/openhands-cli/openhands_cli/setup.py index 1392a3678067..b2ab8ae04966 100644 --- a/openhands-cli/openhands_cli/setup.py +++ b/openhands-cli/openhands_cli/setup.py @@ -1,4 +1,5 @@ import uuid +from typing import Optional from openhands.sdk import BaseConversation, Conversation, LocalFileStore, register_tool from openhands.tools.execute_bash import BashTool @@ -19,15 +20,31 @@ class MissingAgentSpec(Exception): """Raised when agent specification is not found or invalid.""" pass -def setup_conversation() -> BaseConversation: +def setup_conversation(conversation_id: Optional[str] = None) -> BaseConversation: """ Setup the conversation with agent. + Args: + conversation_id: Optional conversation ID to use. If not provided, a random UUID will be generated. + Raises: MissingAgentSpec: If agent specification is not found or invalid. """ - conversation_id = uuid.uuid4() + # Use provided conversation_id or generate a random one + if conversation_id is None: + conversation_id = uuid.uuid4() + else: + # Convert string to UUID if needed + if isinstance(conversation_id, str): + try: + conversation_id = uuid.UUID(conversation_id) + except ValueError: + # If it's not a valid UUID, generate a new one and warn + print_formatted_text( + HTML(f"Warning: '{conversation_id}' is not a valid UUID. Generating a random one.") + ) + conversation_id = uuid.uuid4() with LoadingContext("Initializing OpenHands agent..."): agent_store = AgentStore() diff --git a/openhands-cli/openhands_cli/simple_main.py b/openhands-cli/openhands_cli/simple_main.py index 450ebcc576e3..39ecb03505ab 100644 --- a/openhands-cli/openhands_cli/simple_main.py +++ b/openhands-cli/openhands_cli/simple_main.py @@ -4,6 +4,7 @@ This is a simplified version that demonstrates the TUI functionality. """ +import argparse import logging import os @@ -16,7 +17,6 @@ from openhands_cli.agent_chat import run_cli_entry - def main() -> None: """Main entry point for the OpenHands CLI. @@ -24,10 +24,20 @@ def main() -> None: ImportError: If agent chat dependencies are missing Exception: On other error conditions """ + parser = argparse.ArgumentParser( + description="OpenHands CLI - Terminal User Interface for OpenHands AI Agent" + ) + parser.add_argument( + "--id", + type=str, + help="Conversation ID to use for the session. If not provided, a random UUID will be generated." + ) + + args = parser.parse_args() try: - # Start agent chat directly by default - run_cli_entry() + # Start agent chat with optional conversation ID + run_cli_entry(conversation_id=args.id) except ImportError as e: print_formatted_text( From ca0ec66c43fa9903b01000a8dfd7699f71058ed1 Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 11:53:13 -0400 Subject: [PATCH 02/11] update sha --- openhands-cli/pyproject.toml | 4 ++-- openhands-cli/uv.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openhands-cli/pyproject.toml b/openhands-cli/pyproject.toml index 47936cb0a1a0..4e4b03bbecb7 100644 --- a/openhands-cli/pyproject.toml +++ b/openhands-cli/pyproject.toml @@ -82,5 +82,5 @@ disallow_untyped_defs = true ignore_missing_imports = true [tool.uv.sources] -openhands-sdk = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/sdk", rev = "f0b9bcb5de574f5c4fdc8e1c153bbdd0bf1216ab" } -openhands-tools = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/tools", rev = "f0b9bcb5de574f5c4fdc8e1c153bbdd0bf1216ab" } +openhands-sdk = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/sdk", rev = "9d83da972204eeea5723690cfbf36c9f9ed03628" } +openhands-tools = { git = "https://github.com/All-Hands-AI/agent-sdk.git", subdirectory = "openhands/tools", rev = "9d83da972204eeea5723690cfbf36c9f9ed03628" } diff --git a/openhands-cli/uv.lock b/openhands-cli/uv.lock index edc8cfbe9f7c..99d32142806a 100644 --- a/openhands-cli/uv.lock +++ b/openhands-cli/uv.lock @@ -1484,8 +1484,8 @@ dev = [ [package.metadata] requires-dist = [ - { name = "openhands-sdk", git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Fsdk&rev=f0b9bcb5de574f5c4fdc8e1c153bbdd0bf1216ab" }, - { name = "openhands-tools", git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Ftools&rev=f0b9bcb5de574f5c4fdc8e1c153bbdd0bf1216ab" }, + { name = "openhands-sdk", git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Fsdk&rev=9d83da972204eeea5723690cfbf36c9f9ed03628" }, + { name = "openhands-tools", git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Ftools&rev=9d83da972204eeea5723690cfbf36c9f9ed03628" }, { name = "prompt-toolkit", specifier = ">=3" }, { name = "typer", specifier = ">=0.17.4" }, ] @@ -1505,7 +1505,7 @@ dev = [ [[package]] name = "openhands-sdk" version = "1.0.0" -source = { git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Fsdk&rev=f0b9bcb5de574f5c4fdc8e1c153bbdd0bf1216ab#f0b9bcb5de574f5c4fdc8e1c153bbdd0bf1216ab" } +source = { git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Fsdk&rev=9d83da972204eeea5723690cfbf36c9f9ed03628#9d83da972204eeea5723690cfbf36c9f9ed03628" } dependencies = [ { name = "fastmcp" }, { name = "litellm" }, @@ -1519,7 +1519,7 @@ dependencies = [ [[package]] name = "openhands-tools" version = "1.0.0" -source = { git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Ftools&rev=f0b9bcb5de574f5c4fdc8e1c153bbdd0bf1216ab#f0b9bcb5de574f5c4fdc8e1c153bbdd0bf1216ab" } +source = { git = "https://github.com/All-Hands-AI/agent-sdk.git?subdirectory=openhands%2Ftools&rev=9d83da972204eeea5723690cfbf36c9f9ed03628#9d83da972204eeea5723690cfbf36c9f9ed03628" } dependencies = [ { name = "bashlex" }, { name = "binaryornot" }, From aba171d35f6291ed4c24797c1631468b5075ce11 Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 12:00:44 -0400 Subject: [PATCH 03/11] modify typing --- openhands-cli/openhands_cli/agent_chat.py | 5 +---- openhands-cli/openhands_cli/setup.py | 5 ++--- openhands-cli/openhands_cli/tui/tui.py | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/openhands-cli/openhands_cli/agent_chat.py b/openhands-cli/openhands_cli/agent_chat.py index 35ac52a88ed4..6305a3f19b65 100644 --- a/openhands-cli/openhands_cli/agent_chat.py +++ b/openhands-cli/openhands_cli/agent_chat.py @@ -5,7 +5,6 @@ """ import sys -from typing import Optional from openhands.sdk import ( Message, @@ -40,11 +39,9 @@ def _restore_tty() -> None: pass -def run_cli_entry(conversation_id: Optional[str] = None) -> None: +def run_cli_entry(conversation_id: str | None = None) -> None: """Run the agent chat session using the agent SDK. - Args: - conversation_id: Optional conversation ID to use for the session. Raises: AgentSetupError: If agent setup fails diff --git a/openhands-cli/openhands_cli/setup.py b/openhands-cli/openhands_cli/setup.py index 8918bdff1a9d..47891bc2e9cd 100644 --- a/openhands-cli/openhands_cli/setup.py +++ b/openhands-cli/openhands_cli/setup.py @@ -1,5 +1,4 @@ import uuid -from typing import Optional from openhands.sdk import BaseConversation, Conversation, Workspace, register_tool from openhands.tools.execute_bash import BashTool @@ -20,12 +19,12 @@ class MissingAgentSpec(Exception): """Raised when agent specification is not found or invalid.""" pass -def setup_conversation(conversation_id: Optional[str] = None) -> BaseConversation: +def setup_conversation(conversation_id: str | None = None) -> BaseConversation: """ Setup the conversation with agent. Args: - conversation_id: Optional conversation ID to use. If not provided, a random UUID will be generated. + conversation_id: conversation ID to use. If not provided, a random UUID will be generated. Raises: MissingAgentSpec: If agent specification is not found or invalid. diff --git a/openhands-cli/openhands_cli/tui/tui.py b/openhands-cli/openhands_cli/tui/tui.py index 2d401e75ef82..0ece00830411 100644 --- a/openhands-cli/openhands_cli/tui/tui.py +++ b/openhands-cli/openhands_cli/tui/tui.py @@ -84,7 +84,7 @@ def display_help() -> None: def display_welcome(conversation_id: UUID) -> None: """Display welcome message.""" clear() - display_banner(str(conversation_id)[0:8]) + display_banner(str(conversation_id)) print_formatted_text(HTML("Let's start building!")) print_formatted_text( HTML( From 5743ab88cc9f92563b1aed2d9c9d5ff209367148 Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 15:04:55 -0400 Subject: [PATCH 04/11] add resumed indication --- openhands-cli/openhands_cli/agent_chat.py | 2 +- openhands-cli/openhands_cli/tui/tui.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/openhands-cli/openhands_cli/agent_chat.py b/openhands-cli/openhands_cli/agent_chat.py index 6305a3f19b65..69297149126f 100644 --- a/openhands-cli/openhands_cli/agent_chat.py +++ b/openhands-cli/openhands_cli/agent_chat.py @@ -58,7 +58,7 @@ def run_cli_entry(conversation_id: str | None = None) -> None: except MissingAgentSpec: settings_screen.handle_basic_settings(escapable=False) - display_welcome(conversation.id) + display_welcome(conversation.id, bool(conversation_id)) # Create conversation runner to handle state machine logic runner = ConversationRunner(conversation) diff --git a/openhands-cli/openhands_cli/tui/tui.py b/openhands-cli/openhands_cli/tui/tui.py index 0ece00830411..a1e087f3370e 100644 --- a/openhands-cli/openhands_cli/tui/tui.py +++ b/openhands-cli/openhands_cli/tui/tui.py @@ -43,7 +43,7 @@ def get_completions( ) -def display_banner(conversation_id: str) -> None: +def display_banner(conversation_id: str, resume: bool = False) -> None: print_formatted_text( HTML(r""" ___ _ _ _ @@ -59,7 +59,10 @@ def display_banner(conversation_id: str) -> None: print_formatted_text(HTML(f"OpenHands CLI v{__version__}")) print_formatted_text("") - print_formatted_text(HTML(f"Initialized conversation {conversation_id}")) + if not resume: + print_formatted_text(HTML(f"Initialized conversation {conversation_id}")) + else: + print_formatted_text(HTML(f"Resumed conversation {conversation_id}")) print_formatted_text("") @@ -81,10 +84,10 @@ def display_help() -> None: print_formatted_text("") -def display_welcome(conversation_id: UUID) -> None: +def display_welcome(conversation_id: UUID, resume: bool = False) -> None: """Display welcome message.""" clear() - display_banner(str(conversation_id)) + display_banner(str(conversation_id), resume) print_formatted_text(HTML("Let's start building!")) print_formatted_text( HTML( From 55905badcdac110f1e8b6e91724dc668d9668dfc Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 15:06:24 -0400 Subject: [PATCH 05/11] rename arg --- openhands-cli/openhands_cli/agent_chat.py | 6 +++--- openhands-cli/openhands_cli/simple_main.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openhands-cli/openhands_cli/agent_chat.py b/openhands-cli/openhands_cli/agent_chat.py index 69297149126f..da47c0f27b3b 100644 --- a/openhands-cli/openhands_cli/agent_chat.py +++ b/openhands-cli/openhands_cli/agent_chat.py @@ -39,7 +39,7 @@ def _restore_tty() -> None: pass -def run_cli_entry(conversation_id: str | None = None) -> None: +def run_cli_entry(resume_conversation_id: str | None = None) -> None: """Run the agent chat session using the agent SDK. @@ -54,11 +54,11 @@ def run_cli_entry(conversation_id: str | None = None) -> None: while not conversation: try: - conversation = setup_conversation(conversation_id) + conversation = setup_conversation(resume_conversation_id) except MissingAgentSpec: settings_screen.handle_basic_settings(escapable=False) - display_welcome(conversation.id, bool(conversation_id)) + display_welcome(conversation.id, bool(resume_conversation_id)) # Create conversation runner to handle state machine logic runner = ConversationRunner(conversation) diff --git a/openhands-cli/openhands_cli/simple_main.py b/openhands-cli/openhands_cli/simple_main.py index 39ecb03505ab..b116d231c05b 100644 --- a/openhands-cli/openhands_cli/simple_main.py +++ b/openhands-cli/openhands_cli/simple_main.py @@ -32,12 +32,12 @@ def main() -> None: type=str, help="Conversation ID to use for the session. If not provided, a random UUID will be generated." ) - + args = parser.parse_args() try: # Start agent chat with optional conversation ID - run_cli_entry(conversation_id=args.id) + run_cli_entry(resume_conversation_id=args.id) except ImportError as e: print_formatted_text( From 8b9b56d49fa5c9a81212f9dd918408a9c8af0cbe Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 15:08:02 -0400 Subject: [PATCH 06/11] reraise error --- openhands-cli/openhands_cli/setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/openhands-cli/openhands_cli/setup.py b/openhands-cli/openhands_cli/setup.py index 47891bc2e9cd..db0154e16dfb 100644 --- a/openhands-cli/openhands_cli/setup.py +++ b/openhands-cli/openhands_cli/setup.py @@ -38,12 +38,11 @@ def setup_conversation(conversation_id: str | None = None) -> BaseConversation: if isinstance(conversation_id, str): try: conversation_id = uuid.UUID(conversation_id) - except ValueError: - # If it's not a valid UUID, generate a new one and warn + except ValueError as e: print_formatted_text( - HTML(f"Warning: '{conversation_id}' is not a valid UUID. Generating a random one.") + HTML(f"Warning: '{conversation_id}' is not a valid UUID.") ) - conversation_id = uuid.uuid4() + raise e with LoadingContext("Initializing OpenHands agent..."): agent_store = AgentStore() From 45fe15d632f1ac2009f92cf85750395c5a57277f Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 15:09:13 -0400 Subject: [PATCH 07/11] fix nesting --- openhands-cli/openhands_cli/setup.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openhands-cli/openhands_cli/setup.py b/openhands-cli/openhands_cli/setup.py index db0154e16dfb..7204aab9a4de 100644 --- a/openhands-cli/openhands_cli/setup.py +++ b/openhands-cli/openhands_cli/setup.py @@ -33,16 +33,14 @@ def setup_conversation(conversation_id: str | None = None) -> BaseConversation: # Use provided conversation_id or generate a random one if conversation_id is None: conversation_id = uuid.uuid4() - else: - # Convert string to UUID if needed - if isinstance(conversation_id, str): - try: - conversation_id = uuid.UUID(conversation_id) - except ValueError as e: - print_formatted_text( - HTML(f"Warning: '{conversation_id}' is not a valid UUID.") - ) - raise e + elif isinstance(conversation_id, str): + try: + conversation_id = uuid.UUID(conversation_id) + except ValueError as e: + print_formatted_text( + HTML(f"Warning: '{conversation_id}' is not a valid UUID.") + ) + raise e with LoadingContext("Initializing OpenHands agent..."): agent_store = AgentStore() From 2390205bc73d56490b560b3d7e6c65616717c5c9 Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 15:10:02 -0400 Subject: [PATCH 08/11] fix comment --- openhands-cli/openhands_cli/simple_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openhands-cli/openhands_cli/simple_main.py b/openhands-cli/openhands_cli/simple_main.py index b116d231c05b..5664ad1b8f80 100644 --- a/openhands-cli/openhands_cli/simple_main.py +++ b/openhands-cli/openhands_cli/simple_main.py @@ -36,7 +36,7 @@ def main() -> None: args = parser.parse_args() try: - # Start agent chat with optional conversation ID + # Start agent chat run_cli_entry(resume_conversation_id=args.id) except ImportError as e: From 461a9c584afa92267e91505243222a26f575d6ef Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 15:54:14 -0400 Subject: [PATCH 09/11] print exit hint --- openhands-cli/openhands_cli/agent_chat.py | 13 +++++++++++++ openhands-cli/openhands_cli/simple_main.py | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/openhands-cli/openhands_cli/agent_chat.py b/openhands-cli/openhands_cli/agent_chat.py index da47c0f27b3b..8d7eb4676d5a 100644 --- a/openhands-cli/openhands_cli/agent_chat.py +++ b/openhands-cli/openhands_cli/agent_chat.py @@ -38,6 +38,17 @@ def _restore_tty() -> None: except Exception: pass +def _print_exit_hint(conversation_id: str) -> None: + """Print a resume hint with the current conversation ID.""" + if not conversation_id: + return + print_formatted_text(HTML(f"Conversation ID: {conversation_id}")) + print_formatted_text( + HTML( + f"Hint: run openhands-cli --resume {conversation_id} " + "to resume this conversation." + ) + ) def run_cli_entry(resume_conversation_id: str | None = None) -> None: """Run the agent chat session using the agent SDK. @@ -88,6 +99,7 @@ def run_cli_entry(resume_conversation_id: str | None = None) -> None: exit_confirmation = exit_session_confirmation() if exit_confirmation == UserConfirmation.ACCEPT: print_formatted_text(HTML("\nGoodbye! 👋")) + _print_exit_hint(conversation.id) break elif command == "/settings": @@ -149,6 +161,7 @@ def run_cli_entry(resume_conversation_id: str | None = None) -> None: exit_confirmation = exit_session_confirmation() if exit_confirmation == UserConfirmation.ACCEPT: print_formatted_text(HTML("\nGoodbye! 👋")) + _print_exit_hint(conversation.id) break diff --git a/openhands-cli/openhands_cli/simple_main.py b/openhands-cli/openhands_cli/simple_main.py index 5664ad1b8f80..0dfdbca23dbe 100644 --- a/openhands-cli/openhands_cli/simple_main.py +++ b/openhands-cli/openhands_cli/simple_main.py @@ -28,7 +28,7 @@ def main() -> None: description="OpenHands CLI - Terminal User Interface for OpenHands AI Agent" ) parser.add_argument( - "--id", + "--resume", type=str, help="Conversation ID to use for the session. If not provided, a random UUID will be generated." ) @@ -37,7 +37,7 @@ def main() -> None: try: # Start agent chat - run_cli_entry(resume_conversation_id=args.id) + run_cli_entry(resume_conversation_id=args.resume) except ImportError as e: print_formatted_text( From d69382a264eaaf0762333cc9f6170222074f725b Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 1 Oct 2025 20:03:07 +0000 Subject: [PATCH 10/11] Fix CLI unit tests to work with argparse interface - Updated test_main.py to mock sys.argv instead of internal functions - Fixed all 5 failing tests by adapting to new argparse-based CLI - Added test for --resume argument functionality - All 79 tests now pass Co-authored-by: openhands --- openhands-cli/tests/test_main.py | 75 +++++++++++++++----------------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/openhands-cli/tests/test_main.py b/openhands-cli/tests/test_main.py index e634cc061fbe..16a01507d13a 100644 --- a/openhands-cli/tests/test_main.py +++ b/openhands-cli/tests/test_main.py @@ -11,30 +11,23 @@ class TestMainEntryPoint: """Test the main entry point behavior.""" - @patch('openhands_cli.agent_chat.setup_conversation') - @patch('openhands_cli.agent_chat.ConversationRunner') - @patch('openhands_cli.agent_chat.get_session_prompter') + @patch('openhands_cli.simple_main.run_cli_entry') + @patch('sys.argv', ['openhands-cli']) def test_main_starts_agent_chat_directly( - self, mock_get_session_prompter: MagicMock, mock_runner: MagicMock, mock_setup_conversation: MagicMock + self, mock_run_agent_chat: MagicMock ) -> None: """Test that main() starts agent chat directly when setup succeeds.""" - # Mock setup_conversation to return a valid conversation - mock_conversation = MagicMock() - mock_conversation.id = str(uuid.uuid4()) - mock_setup_conversation.return_value = mock_conversation - - # Mock prompt session to raise KeyboardInterrupt to exit the loop - mock_session = MagicMock() - mock_session.prompt.side_effect = KeyboardInterrupt() - mock_get_session_prompter.return_value = mock_session + # Mock run_cli_entry to raise KeyboardInterrupt to exit gracefully + mock_run_agent_chat.side_effect = KeyboardInterrupt() # Should complete without raising an exception (graceful exit) simple_main.main() - # Should call setup_conversation - mock_setup_conversation.assert_called_once() + # Should call run_cli_entry with no resume conversation ID + mock_run_agent_chat.assert_called_once_with(resume_conversation_id=None) @patch('openhands_cli.simple_main.run_cli_entry') + @patch('sys.argv', ['openhands-cli']) def test_main_handles_import_error(self, mock_run_agent_chat: MagicMock) -> None: """Test that main() handles ImportError gracefully.""" mock_run_agent_chat.side_effect = ImportError('Missing dependency') @@ -45,47 +38,32 @@ def test_main_handles_import_error(self, mock_run_agent_chat: MagicMock) -> None assert str(exc_info.value) == 'Missing dependency' - @patch('openhands_cli.agent_chat.setup_conversation') - @patch('openhands_cli.agent_chat.ConversationRunner') - @patch('openhands_cli.agent_chat.get_session_prompter') + @patch('openhands_cli.simple_main.run_cli_entry') + @patch('sys.argv', ['openhands-cli']) def test_main_handles_keyboard_interrupt( - self, mock_get_session_prompter: MagicMock, mock_runner: MagicMock, mock_setup_conversation: MagicMock + self, mock_run_agent_chat: MagicMock ) -> None: """Test that main() handles KeyboardInterrupt gracefully.""" - # Mock setup_conversation to return a valid conversation - mock_conversation = MagicMock() - mock_conversation.id = str(uuid.uuid4()) - mock_setup_conversation.return_value = mock_conversation - - # Mock prompt session to raise KeyboardInterrupt - mock_session = MagicMock() - mock_session.prompt.side_effect = KeyboardInterrupt() - mock_get_session_prompter.return_value = mock_session + # Mock run_cli_entry to raise KeyboardInterrupt + mock_run_agent_chat.side_effect = KeyboardInterrupt() # Should complete without raising an exception (graceful exit) simple_main.main() - @patch('openhands_cli.agent_chat.setup_conversation') - @patch('openhands_cli.agent_chat.ConversationRunner') - @patch('openhands_cli.agent_chat.get_session_prompter') + @patch('openhands_cli.simple_main.run_cli_entry') + @patch('sys.argv', ['openhands-cli']) def test_main_handles_eof_error( - self, mock_get_session_prompter: MagicMock, mock_runner: MagicMock, mock_setup_conversation: MagicMock + self, mock_run_agent_chat: MagicMock ) -> None: """Test that main() handles EOFError gracefully.""" - # Mock setup_conversation to return a valid conversation - mock_conversation = MagicMock() - mock_conversation.id = str(uuid.uuid4()) - mock_setup_conversation.return_value = mock_conversation - - # Mock prompt session to raise EOFError - mock_session = MagicMock() - mock_session.prompt.side_effect = EOFError() - mock_get_session_prompter.return_value = mock_session + # Mock run_cli_entry to raise EOFError + mock_run_agent_chat.side_effect = EOFError() # Should complete without raising an exception (graceful exit) simple_main.main() @patch('openhands_cli.simple_main.run_cli_entry') + @patch('sys.argv', ['openhands-cli']) def test_main_handles_general_exception( self, mock_run_agent_chat: MagicMock ) -> None: @@ -97,3 +75,18 @@ def test_main_handles_general_exception( simple_main.main() assert str(exc_info.value) == 'Unexpected error' + + @patch('openhands_cli.simple_main.run_cli_entry') + @patch('sys.argv', ['openhands-cli', '--resume', 'test-conversation-id']) + def test_main_with_resume_argument( + self, mock_run_agent_chat: MagicMock + ) -> None: + """Test that main() passes resume conversation ID when provided.""" + # Mock run_cli_entry to raise KeyboardInterrupt to exit gracefully + mock_run_agent_chat.side_effect = KeyboardInterrupt() + + # Should complete without raising an exception (graceful exit) + simple_main.main() + + # Should call run_cli_entry with the provided resume conversation ID + mock_run_agent_chat.assert_called_once_with(resume_conversation_id='test-conversation-id') From cdeab83c88cd6bfbb50917cf636ffbc5b535d09a Mon Sep 17 00:00:00 2001 From: "rohitvinodmalhotra@gmail.com" Date: Wed, 1 Oct 2025 16:05:03 -0400 Subject: [PATCH 11/11] Update agent_chat.py --- openhands-cli/openhands_cli/agent_chat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openhands-cli/openhands_cli/agent_chat.py b/openhands-cli/openhands_cli/agent_chat.py index 8d7eb4676d5a..efadb37ebdc4 100644 --- a/openhands-cli/openhands_cli/agent_chat.py +++ b/openhands-cli/openhands_cli/agent_chat.py @@ -40,8 +40,6 @@ def _restore_tty() -> None: def _print_exit_hint(conversation_id: str) -> None: """Print a resume hint with the current conversation ID.""" - if not conversation_id: - return print_formatted_text(HTML(f"Conversation ID: {conversation_id}")) print_formatted_text( HTML(