diff --git a/devagent/cli.py b/devagent/cli.py index b8826c9..5387408 100644 --- a/devagent/cli.py +++ b/devagent/cli.py @@ -1542,6 +1542,42 @@ def session_compress( console.print(f"[dim]Summary length: {len(result.summary)} chars.[/dim]") +# --------------------------------------------------------------------------- +# Primary agent run command +# --------------------------------------------------------------------------- + +def _pick_session(sessions: list[dict]) -> str | None: + """Interactively choose a session with arrow keys when possible.""" + options = [ + (item["id"], item.get("title") or "Untitled session") + for item in sessions + ] + options.append(("", "Start new session")) + + try: + from prompt_toolkit.shortcuts import radiolist_dialog + + selected = radiolist_dialog( + title="Recent sessions", + text="Use ↑/↓ to select and Enter to confirm.", + values=options, + ).run() + return selected or None + except (ImportError, EOFError, KeyboardInterrupt): + console.print("[bold]Recent sessions[/bold]") + for index, item in enumerate(sessions, 1): + title = item.get("title") or "Untitled session" + console.print(f" [cyan]{index}[/cyan]. {title} ({item['id'][:8]})") + new_index = len(sessions) + 1 + console.print(f" [cyan]{new_index}[/cyan]. Start new session") + choice = console.input("Select a session [default: new]: ").strip() + if choice.isdigit(): + selected = int(choice) + if 1 <= selected <= len(sessions): + return sessions[selected - 1]["id"] + return None + + # --------------------------------------------------------------------------- # Primary agent run command # --------------------------------------------------------------------------- @@ -1598,6 +1634,11 @@ def run( diff_preview: bool = typer.Option( False, "--diff-preview", help="Show a diff and ask for confirmation before each file write/edit" ), + session_picker: bool = typer.Option( + True, + "--session-picker/--no-session-picker", + help="Use arrow keys to select a recent session interactively", + ), ) -> None: """Start an interactive agent session (the main DevAgent command).""" from devagent.agent.flows import DevAgentSession @@ -1615,22 +1656,17 @@ def run( tools_list = [t.strip() for t in allow_tools.split(",")] if allow_tools else None selected_resume = resume - if selected_resume is None and sys.stdin.isatty() and sys.stdout.isatty(): + if ( + selected_resume is None + and session_picker + and sys.stdin.isatty() + and sys.stdout.isatty() + ): from devagent.session.manager import SessionManager sessions = SessionManager().list_by_project(str(project_root), limit=10) if sessions: - console.print("[bold]Recent sessions[/bold]") - for index, item in enumerate(sessions, 1): - title = item.get("title") or "Untitled session" - console.print(f" [cyan]{index}[/cyan]. {title} ({item['id'][:8]})") - new_index = len(sessions) + 1 - console.print(f" [cyan]{new_index}[/cyan]. Start new session") - choice = console.input("Select a session [default: new]: ").strip() - if choice and choice.isdigit(): - selected = int(choice) - if 1 <= selected <= len(sessions): - selected_resume = sessions[selected - 1]["id"] + selected_resume = _pick_session(sessions) try: session = DevAgentSession( diff --git a/tests/test_cli_session_picker.py b/tests/test_cli_session_picker.py new file mode 100644 index 0000000..3469d1e --- /dev/null +++ b/tests/test_cli_session_picker.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +from unittest.mock import Mock + +from devagent import cli + + +def test_pick_session_uses_arrow_picker(monkeypatch) -> None: + sessions = [{"id": "session-one", "title": "First session"}] + dialog = Mock() + dialog.run.return_value = "session-one" + picker = Mock(return_value=dialog) + monkeypatch.setattr("prompt_toolkit.shortcuts.radiolist_dialog", picker) + + assert cli._pick_session(sessions) == "session-one" + picker.assert_called_once() + assert picker.call_args.kwargs["values"] == [ + ("session-one", "First session"), + ("", "Start new session"), + ] + + +def test_pick_session_numeric_fallback(monkeypatch) -> None: + sessions = [ + {"id": "session-one", "title": "First session"}, + {"id": "session-two", "title": "Second session"}, + ] + monkeypatch.setattr( + "prompt_toolkit.shortcuts.radiolist_dialog", + Mock(side_effect=ImportError), + ) + monkeypatch.setattr(cli.console, "input", lambda _: "2") + + assert cli._pick_session(sessions) == "session-two"