feat(sessions,cli,tui): issue #8 — startup agent picker (v0.3.0)
Adds GET /agents fetch + ListView picker for bare `--new` (TUI mode without --agent). Three in-place amendments: - ratatoskr.sessions: new `list_agents()` + `AgentInfo` frozen dataclass with omit-when-null/empty defaults mirroring SessionInfo's INV-001/INV-002 origin-conditional pattern. Non-200 responses raise the existing SessionApiFailed (no new exception). - ratatoskr.cli: `_parse_args` softens `--agent` from absolute to mode-conditional — required for `--send --new`, optional for bare `--new`, forbidden with `--session` (unchanged INV-004). - ratatoskr.tui: new `AgentPickerApp(App[str | None])` — separate Textual App (not Screen-within-RatatoskrApp) so list_agents errors land on real stderr before any alt-screen opens (preserves issue #6's INV-001). `_resolve_then_run` gains a pre-create branch: fetch agents → empty list → exit 13; non-200 → exit 20; network error → exit 21; picker dismissed → exit 0; otherwise thread chosen agent_id into create_session. Contract: docs/contracts/issues/8.contract.md (drift-check clean). Tests: +18 (227 total, was 209). Live smoke against personal Worldtree (:8081) returned 12 agents; programmatic picker drive auto-picked lofn and created a real session with `end_user_id="ratatoskr-tui"`.
This commit is contained in:
+28
-2
@@ -176,11 +176,37 @@ class TestParseArgs:
|
||||
with pytest.raises(UsageError, match="pass exactly one"):
|
||||
_parse_args(["--send", "hi", "--api-key", "k"])
|
||||
|
||||
def test_usage_new_without_agent(self) -> None:
|
||||
"""usage_new_without_agent: --new without --agent → UsageError."""
|
||||
def test_usage_send_new_without_agent(self) -> None:
|
||||
"""send_new_without_agent (issue #8): --send --new without --agent → UsageError.
|
||||
|
||||
--send mode is non-interactive — cannot prompt; --agent stays required.
|
||||
"""
|
||||
with pytest.raises(UsageError, match="--agent is required when --new"):
|
||||
_parse_args(["--send", "hi", "--new", "--api-key", "k"])
|
||||
|
||||
def test_parse_bare_new_without_agent_accepted(self) -> None:
|
||||
"""bare_new_without_agent (issue #8): --new without --send or --agent → agent_id=None.
|
||||
|
||||
TUI mode CAN prompt; startup picker handles the choice. _parse_args
|
||||
accepts None here and the TUI's _resolve_then_run drives the picker.
|
||||
"""
|
||||
args = _parse_args(["--new", "--api-key", "k"])
|
||||
assert args.new is True
|
||||
assert args.agent_id is None
|
||||
assert args.send_content is None
|
||||
|
||||
def test_parse_bare_new_with_agent_accepted(self) -> None:
|
||||
"""bare_new_with_agent (issue #8): --new --agent mimir (no --send) → picker skipped.
|
||||
|
||||
Existing TUI launch path with an explicit agent_id continues to work
|
||||
— _resolve_then_run sees `args.agent_id is not None` and skips the
|
||||
picker entirely.
|
||||
"""
|
||||
args = _parse_args(["--new", "--agent", "mimir", "--api-key", "k"])
|
||||
assert args.new is True
|
||||
assert args.agent_id == "mimir"
|
||||
assert args.send_content is None
|
||||
|
||||
def test_usage_session_with_agent(self) -> None:
|
||||
"""usage_session_with_agent: --session AND --agent → UsageError."""
|
||||
with pytest.raises(UsageError, match="forbidden with --session"):
|
||||
|
||||
Reference in New Issue
Block a user