mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
feat(cli): add agent state validation to /resume command (#10066)
This commit is contained in:
parent
d233e89873
commit
a36d1673fa
@ -127,6 +127,7 @@ async def handle_commands(
|
||||
config: OpenHandsConfig,
|
||||
current_dir: str,
|
||||
settings_store: FileSettingsStore,
|
||||
agent_state: str,
|
||||
) -> tuple[bool, bool, bool, ExitReason]:
|
||||
close_repl = False
|
||||
reload_microagents = False
|
||||
@ -159,7 +160,9 @@ async def handle_commands(
|
||||
elif command == '/settings':
|
||||
await handle_settings_command(config, settings_store)
|
||||
elif command == '/resume':
|
||||
close_repl, new_session_requested = await handle_resume_command(event_stream)
|
||||
close_repl, new_session_requested = await handle_resume_command(
|
||||
event_stream, agent_state
|
||||
)
|
||||
elif command == '/mcp':
|
||||
await handle_mcp_command(config)
|
||||
else:
|
||||
@ -292,10 +295,20 @@ async def handle_settings_command(
|
||||
# This is a workaround to handle the resume command for the time being. Replace user message with the state change event once the issue is fixed.
|
||||
async def handle_resume_command(
|
||||
event_stream: EventStream,
|
||||
agent_state: str,
|
||||
) -> tuple[bool, bool]:
|
||||
close_repl = True
|
||||
new_session_requested = False
|
||||
|
||||
if agent_state != AgentState.PAUSED:
|
||||
close_repl = False
|
||||
print_formatted_text(
|
||||
HTML(
|
||||
'<ansired>Error: Agent is not paused. /resume command is only available when agent is paused.</ansired>'
|
||||
)
|
||||
)
|
||||
return close_repl, new_session_requested
|
||||
|
||||
event_stream.add_event(
|
||||
MessageAction(content='continue'),
|
||||
EventSource.USER,
|
||||
|
||||
@ -190,6 +190,7 @@ async def run_session(
|
||||
config,
|
||||
current_dir,
|
||||
settings_store,
|
||||
agent_state,
|
||||
)
|
||||
|
||||
if close_repl:
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from prompt_toolkit.formatted_text import HTML
|
||||
|
||||
from openhands.cli.commands import (
|
||||
display_mcp_servers,
|
||||
@ -32,6 +33,7 @@ class TestHandleCommands:
|
||||
config = MagicMock(spec=OpenHandsConfig)
|
||||
current_dir = '/test/dir'
|
||||
settings_store = MagicMock(spec=FileSettingsStore)
|
||||
agent_state = AgentState.RUNNING
|
||||
|
||||
return {
|
||||
'event_stream': event_stream,
|
||||
@ -40,6 +42,7 @@ class TestHandleCommands:
|
||||
'config': config,
|
||||
'current_dir': current_dir,
|
||||
'settings_store': settings_store,
|
||||
'agent_state': agent_state,
|
||||
}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@ -562,13 +565,16 @@ class TestHandleSettingsCommand:
|
||||
|
||||
class TestHandleResumeCommand:
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_resume_command(self):
|
||||
"""Test that handle_resume_command adds the 'continue' message to the event stream."""
|
||||
@patch('openhands.cli.commands.print_formatted_text')
|
||||
async def test_handle_resume_command_paused_state(self, mock_print):
|
||||
"""Test that handle_resume_command works when agent is in PAUSED state."""
|
||||
# Create a mock event stream
|
||||
event_stream = MagicMock(spec=EventStream)
|
||||
|
||||
# Call the function
|
||||
close_repl, new_session_requested = await handle_resume_command(event_stream)
|
||||
# Call the function with PAUSED state
|
||||
close_repl, new_session_requested = await handle_resume_command(
|
||||
event_stream, AgentState.PAUSED
|
||||
)
|
||||
|
||||
# Check that the event stream add_event was called with the correct message action
|
||||
event_stream.add_event.assert_called_once()
|
||||
@ -583,6 +589,40 @@ class TestHandleResumeCommand:
|
||||
assert close_repl is True
|
||||
assert new_session_requested is False
|
||||
|
||||
# Verify no error message was printed
|
||||
mock_print.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
'invalid_state', [AgentState.RUNNING, AgentState.FINISHED, AgentState.ERROR]
|
||||
)
|
||||
@patch('openhands.cli.commands.print_formatted_text')
|
||||
async def test_handle_resume_command_invalid_states(
|
||||
self, mock_print, invalid_state
|
||||
):
|
||||
"""Test that handle_resume_command shows error for all non-PAUSED states."""
|
||||
event_stream = MagicMock(spec=EventStream)
|
||||
|
||||
close_repl, new_session_requested = await handle_resume_command(
|
||||
event_stream, invalid_state
|
||||
)
|
||||
|
||||
# Check that no event was added to the stream
|
||||
event_stream.add_event.assert_not_called()
|
||||
|
||||
# Verify print was called with the error message
|
||||
assert mock_print.call_count == 1
|
||||
error_call = mock_print.call_args_list[0][0][0]
|
||||
assert isinstance(error_call, HTML)
|
||||
assert 'Error: Agent is not paused' in str(error_call)
|
||||
assert '/resume command is only available when agent is paused' in str(
|
||||
error_call
|
||||
)
|
||||
|
||||
# Check the return values
|
||||
assert close_repl is False
|
||||
assert new_session_requested is False
|
||||
|
||||
|
||||
class TestMCPErrorHandling:
|
||||
"""Test MCP error handling in commands."""
|
||||
|
||||
@ -248,6 +248,7 @@ class TestCliCommandsPauseResume:
|
||||
config = MagicMock()
|
||||
current_dir = '/test/dir'
|
||||
settings_store = MagicMock()
|
||||
agent_state = AgentState.PAUSED
|
||||
|
||||
# Mock return value
|
||||
mock_handle_resume.return_value = (False, False)
|
||||
@ -266,10 +267,11 @@ class TestCliCommandsPauseResume:
|
||||
config,
|
||||
current_dir,
|
||||
settings_store,
|
||||
agent_state,
|
||||
)
|
||||
|
||||
# Check that handle_resume_command was called with correct args
|
||||
mock_handle_resume.assert_called_once_with(event_stream)
|
||||
mock_handle_resume.assert_called_once_with(event_stream, agent_state)
|
||||
|
||||
# Check the return values
|
||||
assert close_repl is False
|
||||
|
||||
@ -4,6 +4,7 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from openhands.cli.commands import handle_commands
|
||||
from openhands.core.schema import AgentState
|
||||
from openhands.core.schema.exit_reason import ExitReason
|
||||
|
||||
|
||||
@ -51,6 +52,7 @@ async def test_handle_exit_command_returns_intentional(monkeypatch):
|
||||
MagicMock(),
|
||||
'/tmp/test',
|
||||
MagicMock(),
|
||||
AgentState.RUNNING,
|
||||
)
|
||||
|
||||
assert exit_reason == ExitReason.INTENTIONAL
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user