feat(tui): delete sessions from /resume picker with d#17668
Conversation
Pressing `d` on the highlighted row in the resume picker prompts `delete? y/n`; `y` deletes the session (DB row + on-disk transcript files), anything else cancels. The active session is excluded from deletion server-side. Adds a new `session.delete` JSON-RPC handler that wraps `SessionDB.delete_session`, forwarding the per-profile `sessions/` directory so transcripts get cleaned up alongside the row.
Single-key confirm matches how the picker already accepts 1-9 to resume — no separate y/n keymap to learn — and "press d again" is self-documenting next to the cursor.
There was a problem hiding this comment.
Pull request overview
Adds session deletion to the TUI /resume picker, backed by a new gateway JSON-RPC handler that deletes both the DB row and on-disk transcript artifacts, with tests to cover the server-side behavior.
Changes:
- Add
session.deleteJSON-RPC handler in the gateway (with active-session refusal and transcript cleanup). - Add
d-key delete flow in the/resumesession picker UI and wire it tosession.delete. - Add gateway response typing and server-side handler tests.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| ui-tui/src/gatewayTypes.ts | Adds SessionDeleteResponse type for the new RPC response shape. |
| ui-tui/src/components/sessionPicker.tsx | Implements delete interaction in the /resume picker and calls session.delete. |
| tui_gateway/server.py | Implements session.delete RPC handler and guards against deleting active sessions. |
| tests/test_tui_gateway_server.py | Adds unit tests covering session.delete success and error cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…sessions If a concurrent RPC mutates _sessions while session.delete is iterating it (e.g. a parallel session.create on the thread pool), the bare except swallowed the RuntimeError and let the delete proceed against a row that may still be live. Snapshot via list(_sessions.values()) and return an error when even that raises, instead of treating "couldn't check" as "no active sessions."
There was a problem hiding this comment.
Pull request overview
Adds end-to-end support for deleting saved sessions directly from the TUI /resume picker (double-tap d to confirm), with a backend guard to prevent deleting any session currently active in this gateway process.
Changes:
- Add
session.deleteJSON-RPC handler that deletes a session and associated on-disk transcript files, while refusing deletion of active sessions (fail-closed if active-session enumeration is unsafe). - Update the TUI session picker to support “press
dagain to delete” confirmation and in-place list updates. - Add frontend typing for the new RPC response and comprehensive backend handler tests.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| ui-tui/src/gatewayTypes.ts | Adds SessionDeleteResponse type for the new session.delete RPC. |
| ui-tui/src/components/sessionPicker.tsx | Implements double-d delete confirmation UX, calls session.delete, updates list without reopening picker, and surfaces errors inline. |
| tui_gateway/server.py | Introduces session.delete handler wrapping SessionDB.delete_session, forwards sessions_dir, refuses active-session deletion, and fails closed on concurrent _sessions enumeration issues. |
| tests/test_tui_gateway_server.py | Adds focused tests covering validation, DB-unavailable behavior, active-session refusal, fail-closed snapshot errors, missing sessions, exception propagation, and success path. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Pressing
don the highlighted row in the/resumepicker arms adeletion confirmation: the title cell shows
press d again to delete.A second
d/Dconfirms; any other key cancels. The list updatesin-place — no need to reopen the picker. The active session is
refused server-side so a racing caller can't pull it out from under
the live agent.
Changes
tui_gateway/server.py— newsession.deleteJSON-RPC handler.Wraps
SessionDB.delete_session, forwards the per-profilesessions/directory so transcript files get cleaned up alongsidethe row, and refuses deletion of any session currently bound to a
live TUI session in this gateway process. The active-session check
takes a
list(_sessions.values())snapshot and fails closed —if the snapshot itself raises (concurrent mutation by another RPC),
the handler returns an error instead of silently allowing the
delete.
ui-tui/src/components/sessionPicker.tsx— firstdpress armsthe row, second
ddeletes viagw.request('session.delete')andremoves the row from the rendered list. Anything else cancels.
ui-tui/src/gatewayTypes.ts—SessionDeleteResponsetype.tests/test_tui_gateway_server.py— seven handler tests:missing id, no DB, active-session refusal, fail-closed when the
active-session snapshot raises, missing row, exception propagation,
success carries
{deleted}and forwardssessions_dir.Test plan
scripts/run_tests.sh tests/test_tui_gateway_server.py -k session_delete— 7/7 passscripts/run_tests.sh tests/test_tui_gateway_server.py— 141 pass (the 2 pre-existing failures onorigin/mainare unchanged)cd ui-tui && npm run type-check— cleancd ui-tui && npm test— 511/511 pass/resume, pressd, confirm with secondd, see row disappear