mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
6 Commits
debug-logg
...
cli-task-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12213d0756 | ||
|
|
b234a4ba0b | ||
|
|
f5b7f39bee | ||
|
|
fc3c22b9f2 | ||
|
|
54e790f030 | ||
|
|
2e069c7e78 |
@@ -7,10 +7,7 @@ Provides a conversation interface with an AI agent using OpenHands patterns.
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from openhands.sdk import (
|
||||
Message,
|
||||
TextContent,
|
||||
)
|
||||
from openhands.sdk import Message, TextContent
|
||||
from openhands.sdk.conversation.state import AgentExecutionStatus
|
||||
from prompt_toolkit import print_formatted_text
|
||||
from prompt_toolkit.formatted_text import HTML
|
||||
@@ -20,10 +17,7 @@ from openhands_cli.setup import MissingAgentSpec, setup_conversation, start_fres
|
||||
from openhands_cli.tui.settings.mcp_screen import MCPScreen
|
||||
from openhands_cli.tui.settings.settings_screen import SettingsScreen
|
||||
from openhands_cli.tui.status import display_status
|
||||
from openhands_cli.tui.tui import (
|
||||
display_help,
|
||||
display_welcome,
|
||||
)
|
||||
from openhands_cli.tui.tui import display_help, display_welcome
|
||||
from openhands_cli.user_actions import UserConfirmation, exit_session_confirmation
|
||||
from openhands_cli.user_actions.utils import get_session_prompter
|
||||
|
||||
@@ -43,6 +37,9 @@ def _restore_tty() -> None:
|
||||
|
||||
def _print_exit_hint(conversation_id: str) -> None:
|
||||
"""Print a resume hint with the current conversation ID."""
|
||||
from prompt_toolkit import print_formatted_text
|
||||
from prompt_toolkit.formatted_text import HTML
|
||||
|
||||
print_formatted_text(
|
||||
HTML(f'<grey>Conversation ID:</grey> <yellow>{conversation_id}</yellow>')
|
||||
)
|
||||
@@ -55,9 +52,11 @@ def _print_exit_hint(conversation_id: str) -> None:
|
||||
|
||||
|
||||
|
||||
def run_cli_entry(resume_conversation_id: str | None = None) -> None:
|
||||
def run_cli_entry(resume_conversation_id: str | None = None, initial_user_message: str | None = None) -> None:
|
||||
"""Run the agent chat session using the agent SDK.
|
||||
|
||||
If initial_user_message is provided, it will be sent once before
|
||||
entering the interactive prompt loop.
|
||||
|
||||
Raises:
|
||||
AgentSetupError: If agent setup fails
|
||||
@@ -82,6 +81,13 @@ def run_cli_entry(resume_conversation_id: str | None = None) -> None:
|
||||
runner = ConversationRunner(conversation)
|
||||
session = get_session_prompter()
|
||||
|
||||
# If an initial message was provided and we're not resuming, send it once
|
||||
if (not resume_conversation_id) and initial_user_message and initial_user_message.strip():
|
||||
runner.process_message(
|
||||
Message(role='user', content=[TextContent(text=initial_user_message)])
|
||||
)
|
||||
print()
|
||||
|
||||
# Main chat loop
|
||||
while True:
|
||||
try:
|
||||
|
||||
@@ -31,6 +31,17 @@ Examples:
|
||||
help='Conversation ID to resume'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--task',
|
||||
type=str,
|
||||
help='Initial task prompt to send once at startup'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--file',
|
||||
type=str,
|
||||
help='Path to a file whose content will be sent as the initial message'
|
||||
)
|
||||
|
||||
# Only serve as subcommand
|
||||
subparsers = parser.add_subparsers(
|
||||
dest='command',
|
||||
|
||||
@@ -20,6 +20,19 @@ from prompt_toolkit.formatted_text import HTML
|
||||
from openhands_cli.argparsers.main_parser import create_main_parser
|
||||
|
||||
|
||||
def _build_initial_user_message(args) -> str | None:
|
||||
if args.file:
|
||||
try:
|
||||
with open(args.file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
if not content.strip():
|
||||
return args.task
|
||||
return content
|
||||
except Exception:
|
||||
return args.task
|
||||
return args.task
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main entry point for the OpenHands CLI.
|
||||
|
||||
@@ -41,8 +54,12 @@ def main() -> None:
|
||||
# Import agent_chat only when needed
|
||||
from openhands_cli.agent_chat import run_cli_entry
|
||||
|
||||
initial_user_message = _build_initial_user_message(args)
|
||||
# Start agent chat
|
||||
run_cli_entry(resume_conversation_id=args.resume)
|
||||
kwargs = {"resume_conversation_id": args.resume}
|
||||
if initial_user_message:
|
||||
kwargs["initial_user_message"] = initial_user_message
|
||||
run_cli_entry(**kwargs)
|
||||
except KeyboardInterrupt:
|
||||
print_formatted_text(HTML('\n<yellow>Goodbye! 👋</yellow>'))
|
||||
except EOFError:
|
||||
|
||||
86
openhands-cli/tests/test_initial_message.py
Normal file
86
openhands-cli/tests/test_initial_message.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import os
|
||||
import sys
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from openhands_cli.simple_main import _build_initial_user_message, main
|
||||
|
||||
|
||||
def test_build_initial_user_message_task_only(tmp_path):
|
||||
args = SimpleNamespace(file=None, task="Do the thing")
|
||||
assert _build_initial_user_message(args) == "Do the thing"
|
||||
|
||||
|
||||
def test_build_initial_user_message_file_precedence(tmp_path):
|
||||
p = tmp_path / "input.txt"
|
||||
p.write_text("From file content", encoding="utf-8")
|
||||
args = SimpleNamespace(file=str(p), task="Fallback task")
|
||||
assert _build_initial_user_message(args) == "From file content"
|
||||
|
||||
|
||||
def test_build_initial_user_message_file_missing_falls_back_to_task(tmp_path):
|
||||
args = SimpleNamespace(file=str(tmp_path / "missing.txt"), task="Use task")
|
||||
assert _build_initial_user_message(args) == "Use task"
|
||||
|
||||
|
||||
def test_build_initial_user_message_empty_file_falls_back_to_task(tmp_path):
|
||||
p = tmp_path / "empty.txt"
|
||||
p.write_text("\n\n\t ", encoding="utf-8")
|
||||
args = SimpleNamespace(file=str(p), task="Use task")
|
||||
assert _build_initial_user_message(args) == "Use task"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"argv, expected_kwargs",
|
||||
[
|
||||
(['openhands', '--task', 'Hello'], {"resume_conversation_id": None, "initial_user_message": "Hello"}),
|
||||
],
|
||||
)
|
||||
@patch('openhands_cli.agent_chat.run_cli_entry')
|
||||
def test_main_passes_initial_message_from_task(mock_run, monkeypatch, argv, expected_kwargs):
|
||||
monkeypatch.setattr(sys, "argv", argv, raising=False)
|
||||
mock_run.side_effect = KeyboardInterrupt()
|
||||
main()
|
||||
mock_run.assert_called_once_with(**expected_kwargs)
|
||||
|
||||
|
||||
@patch('openhands_cli.agent_chat.run_cli_entry')
|
||||
def test_main_passes_initial_message_from_file_precedence(mock_run, monkeypatch, tmp_path):
|
||||
p = tmp_path / "input.txt"
|
||||
p.write_text("Content A", encoding="utf-8")
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
'openhands', '--task', 'Task B', '--file', str(p)
|
||||
], raising=False)
|
||||
mock_run.side_effect = KeyboardInterrupt()
|
||||
main()
|
||||
mock_run.assert_called_once()
|
||||
call_kwargs = mock_run.call_args.kwargs
|
||||
assert call_kwargs["resume_conversation_id"] is None
|
||||
assert call_kwargs["initial_user_message"] == "Content A"
|
||||
|
||||
|
||||
@patch('openhands_cli.agent_chat.run_cli_entry')
|
||||
def test_main_passes_task_when_file_missing(mock_run, monkeypatch, tmp_path):
|
||||
missing = tmp_path / "missing.txt"
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
'openhands', '--task', 'Fallback', '--file', str(missing)
|
||||
], raising=False)
|
||||
mock_run.side_effect = KeyboardInterrupt()
|
||||
main()
|
||||
call_kwargs = mock_run.call_args.kwargs
|
||||
assert call_kwargs["initial_user_message"] == "Fallback"
|
||||
|
||||
|
||||
@patch('openhands_cli.agent_chat.run_cli_entry')
|
||||
def test_main_passes_task_when_file_empty(mock_run, monkeypatch, tmp_path):
|
||||
p = tmp_path / "empty.txt"
|
||||
p.write_text("\n \t", encoding="utf-8")
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
'openhands', '--task', 'TaskText', '--file', str(p)
|
||||
], raising=False)
|
||||
mock_run.side_effect = KeyboardInterrupt()
|
||||
main()
|
||||
call_kwargs = mock_run.call_args.kwargs
|
||||
assert call_kwargs["initial_user_message"] == "TaskText"
|
||||
Reference in New Issue
Block a user