diff --git a/openhands/cli/main.py b/openhands/cli/main.py index d8c07ef63d..7247ce9ad7 100644 --- a/openhands/cli/main.py +++ b/openhands/cli/main.py @@ -77,7 +77,6 @@ async def cleanup_session( controller: AgentController, ) -> None: """Clean up all resources from the current session.""" - event_stream = runtime.event_stream end_state = controller.get_state() end_state.save_to_session( @@ -434,7 +433,23 @@ async def main_with_loop(loop: asyncio.AbstractEventLoop) -> None: return # Read task from file, CLI args, or stdin - task_str = read_task(args, config.cli_multiline_input) + if args.file: + # For CLI usage, we want to enhance the file content with a prompt + # that instructs the agent to read and understand the file first + with open(args.file, 'r', encoding='utf-8') as file: + file_content = file.read() + + # Create a prompt that instructs the agent to read and understand the file first + task_str = f"""The user has tagged a file '{args.file}'. +Please read and understand the following file content first: + +``` +{file_content} +``` + +After reviewing the file, please ask the user what they would like to do with it.""" + else: + task_str = read_task(args, config.cli_multiline_input) # Run the first session new_session_requested = await run_session( diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 7013a90b4b..4355ecc331 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -345,6 +345,7 @@ async def test_main_without_task( mock_args.agent_cls = None mock_args.llm_config = None mock_args.name = None + mock_args.file = None mock_parse_args.return_value = mock_args # Mock config @@ -427,6 +428,7 @@ async def test_main_with_task( mock_args = MagicMock() mock_args.agent_cls = 'custom-agent' mock_args.llm_config = 'custom-config' + mock_args.file = None mock_parse_args.return_value = mock_args # Mock config @@ -523,6 +525,7 @@ async def test_main_with_session_name_passes_name_to_run_session( mock_args.agent_cls = None mock_args.llm_config = None mock_args.name = test_session_name # Set the session name + mock_args.file = None mock_parse_args.return_value = mock_args # Mock config @@ -831,3 +834,93 @@ async def test_config_loading_order( # Verify that run_session was called with the correct arguments mock_run_session.assert_called_once() + + +@pytest.mark.asyncio +@patch('openhands.cli.main.parse_arguments') +@patch('openhands.cli.main.setup_config_from_args') +@patch('openhands.cli.main.FileSettingsStore.get_instance') +@patch('openhands.cli.main.check_folder_security_agreement') +@patch('openhands.cli.main.run_session') +@patch('openhands.cli.main.LLMSummarizingCondenserConfig') +@patch('openhands.cli.main.NoOpCondenserConfig') +@patch('builtins.open', new_callable=MagicMock) +async def test_main_with_file_option( + mock_open, + mock_noop_condenser, + mock_llm_condenser, + mock_run_session, + mock_check_security, + mock_get_settings_store, + mock_setup_config, + mock_parse_args, +): + """Test main function with a file option.""" + loop = asyncio.get_running_loop() + + # Mock arguments + mock_args = MagicMock() + mock_args.agent_cls = None + mock_args.llm_config = None + mock_args.name = None + mock_args.file = '/path/to/test/file.txt' + mock_args.task = None + mock_parse_args.return_value = mock_args + + # Mock config + mock_config = MagicMock() + mock_config.workspace_base = '/test/dir' + mock_config.cli_multiline_input = False + mock_setup_config.return_value = mock_config + + # Mock settings store + mock_settings_store = AsyncMock() + mock_settings = MagicMock() + mock_settings.agent = 'test-agent' + mock_settings.llm_model = 'test-model' + mock_settings.llm_api_key = 'test-api-key' + mock_settings.llm_base_url = 'test-base-url' + mock_settings.confirmation_mode = True + mock_settings.enable_default_condenser = True + mock_settings_store.load.return_value = mock_settings + mock_get_settings_store.return_value = mock_settings_store + + # Mock condenser config to return a mock instead of validating + mock_llm_condenser_instance = MagicMock() + mock_llm_condenser.return_value = mock_llm_condenser_instance + + # Mock security check + mock_check_security.return_value = True + + # Mock file open + mock_file = MagicMock() + mock_file.__enter__.return_value.read.return_value = 'This is a test file content.' + mock_open.return_value = mock_file + + # Mock run_session to return False (no new session requested) + mock_run_session.return_value = False + + # Run the function + await cli.main_with_loop(loop) + + # Assertions + mock_parse_args.assert_called_once() + mock_setup_config.assert_called_once_with(mock_args) + mock_get_settings_store.assert_called_once() + mock_settings_store.load.assert_called_once() + mock_check_security.assert_called_once_with(mock_config, '/test/dir') + + # Verify file was opened + mock_open.assert_called_once_with('/path/to/test/file.txt', 'r', encoding='utf-8') + + # Check that run_session was called with expected arguments + mock_run_session.assert_called_once() + # Extract the task_str from the call + task_str = mock_run_session.call_args[0][4] + assert "The user has tagged a file '/path/to/test/file.txt'" in task_str + assert 'Please read and understand the following file content first:' in task_str + assert 'This is a test file content.' in task_str + assert ( + 'After reviewing the file, please ask the user what they would like to do with it.' + in task_str + )