feat: add /sessions command to list sessions and switch to session#90
Conversation
632771a to
cfd30b6
Compare
|
I just refactored the cli entrypoint today. Now the main branch is a bit off with this PR. Could you please have a look and resolve the conflict? BTW, I guess it would be way easier to raise a |
|
I tried using Reload exception as suggested, but it causes a UX issue: the shell restarts and clears the screen, so users can't see their previous conversation history. The direct state replacement approach provides better UX. |
|
And I resolve the conflict issue in the newest commit but still use the old logical |
cfd30b6 to
3d76931
Compare
Signed-off-by: Richard Chien <stdrc@outlook.com>
Signed-off-by: Richard Chien <stdrc@outlook.com>
Signed-off-by: Richard Chien <stdrc@outlook.com>
Signed-off-by: Richard Chien <stdrc@outlook.com>
3d76931 to
9966846
Compare
There was a problem hiding this comment.
Pull request overview
This PR implements session management functionality with a new /sessions (alias /resume) meta command that allows users to list and interactively switch between conversation sessions. The feature includes proper session metadata extraction (titles from first user input, timestamps), interactive session selection UI, and full conversation history restoration upon switching.
Key Changes:
- Added
Session.refresh()method to extract session titles from wire files and update metadata - Implemented
/sessionsmeta command with interactive selection UI usingChoiceInput - Refactored
Reloadexception to support session switching with optionalsession_idparameter
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/kimi_cli/session.py | Made Session mutable, added refresh() method for title extraction from wire files, added wire_file property, refactored session creation/finding to use refresh |
| src/kimi_cli/ui/shell/metacmd.py | Added list_sessions meta command to display sessions with previews and allow interactive switching |
| src/kimi_cli/cli.py | Enhanced Reload exception with session_id parameter and modified main loop to support session switching |
| src/kimi_cli/utils/datetime.py | Added format_relative_time() utility for human-friendly timestamp formatting |
| src/kimi_cli/soul/kimisoul.py | Renamed wire_file_backend property to wire_file to match Session API |
| src/kimi_cli/ui/wire/init.py | Updated to use renamed wire_file property |
| src/kimi_cli/ui/shell/init.py | Updated to use renamed wire_file property |
| src/kimi_cli/ui/print/init.py | Updated to use renamed wire_file property |
| src/kimi_cli/soul/init.py | Updated parameter name from wire_file_backend to wire_file |
| src/kimi_cli/wire/serde.py | Updated deserialize_wire_message type signature to accept Any for error handling tests |
| tests/test_session.py | Added comprehensive tests for session creation, finding, listing, and title extraction |
| tests/test_wire_message.py | Added test for invalid wire message deserialization error handling |
Comments suppressed due to low confidence (1)
src/kimi_cli/session.py:38
- The Session dataclass is modified from
frozen=Trueto mutable (removing frozen), but the fieldstitleandupdated_atare marked as "refreshable metadata" while other fields are "static metadata". This creates potential for inconsistent state if the session object is shared or cached. Consider whether mutability is necessary, or if creating a new Session instance inrefresh()would be safer. If mutability is intentional, document the thread-safety implications and expected usage patterns.
@dataclass(slots=True, kw_only=True)
class Session:
"""A session of a work directory."""
# static metadata
id: str
"""The session ID."""
work_dir: KaosPath
"""The absolute path of the work directory."""
work_dir_meta: WorkDirMeta
"""The metadata of the work directory."""
context_file: Path
"""The absolute path to the file storing the message history."""
# refreshable metadata
title: str
"""The title of the session."""
updated_at: float
"""The timestamp of the last update to the session."""
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @meta_command(name="sessions", aliases=["resume"], kimi_soul_only=True) | ||
| async def list_sessions(app: Shell, args: list[str]): | ||
| """List sessions and resume optionally""" | ||
| assert isinstance(app.soul, KimiSoul) | ||
|
|
||
| work_dir = app.soul._runtime.session.work_dir | ||
| current_session_id = app.soul._runtime.session.id | ||
| sessions = await Session.list(work_dir) | ||
|
|
||
| if not sessions: | ||
| console.print("[yellow]No sessions found.[/yellow]") | ||
| return | ||
|
|
||
| choices: list[tuple[str, str]] = [] | ||
| for session in sessions: | ||
| time_str = format_relative_time(session.updated_at) | ||
| marker = " (current)" if session.id == current_session_id else "" | ||
| label = f"{session.title}, {time_str}{marker}" | ||
| choices.append((session.id, label)) | ||
|
|
||
| try: | ||
| selection = await ChoiceInput( | ||
| message="Select a session to switch to (↑↓ navigate, Enter select, Ctrl+C cancel):", | ||
| options=choices, | ||
| default=choices[0][0], | ||
| ).prompt_async() | ||
| except (EOFError, KeyboardInterrupt): | ||
| return | ||
|
|
||
| if not selection: | ||
| return | ||
|
|
||
| if selection == current_session_id: | ||
| console.print("[yellow]You are already in this session.[/yellow]") | ||
| return | ||
|
|
||
| console.print(f"[green]Switching to session {selection}...[/green]") | ||
| raise Reload(session_id=selection) |
There was a problem hiding this comment.
The new /sessions (alias /resume) metacommand lacks test coverage. The repository has comprehensive tests for meta commands (test_metacmd.py) and other functionality, but there are no tests verifying the behavior of the list_sessions command, including edge cases like empty session lists, current session selection, user cancellation, or session switching logic.
Fix #83