AutoGPT is the vision of accessible AI for everyone, to use and to build on. Our mission is to provide the tools, so that you can focus on what matters.
feat(backend/copilot-bot): add /resume to pick and resume a past chat (#13219)
### Why / What / How > **Stacked on #13199 (`/new`)** — both touch `commands.py` and this needs `sessions.py`. PR base is `fix/copilot-bot-new-command`; GitHub auto-retargets to `dev` once #13199 merges. **Why:** With the bot live, AutoPilot DM users have no way to resume an existing chat from the platform — they're stuck with whatever session the DM happens to be pointing at. The web app has the chat history; the bot should let users hop back into a specific past conversation. **What:** A new DM-only `/resume` slash command. It lists the user's most-recent AutoPilot conversations in a Discord dropdown (up to 25, title + last-active relative time). Picking one points the DM's session cache at that `session_id` so the next message continues that exact chat. The command is rejected in servers and threads. **How:** - **Backend (RPC, server-side):** new `list_user_chats(platform, platform_user_id, limit, offset)` on `PlatformLinkingManager`. It resolves the caller's AutoGPT user via `find_user_link_owner(platform, platform_user_id)` — the same composite-unique-key lookup `start_chat_turn` already uses for DMs — and calls `get_user_sessions(owner)` (which filters strictly `where userId = owner`). The bot only ever passes its caller's Discord id; the bot process never holds an AutoGPT `user_id`. A slim response model (`ChatSessionSummary` = `session_id` + `title` + `updated_at`) keeps `user_id`/credentials off the wire entirely. - **Bot facade:** `BotBackend.list_user_chats` calls the RPC and returns `list[ChatSummary]`. - **Cache write on pick:** `sessions.set_session()` (mirror of `clear_session` from `/new`) writes the picked id under the same key the handler reads (`copilot-bot:session:<platform>:<target_id>`), so the next message in the DM resumes that chat. - **DM-only enforcement:** `_handle_resume` rejects when `interaction.guild is not None`. This is the load-bearing privacy guard: server-context ownership resolves to the server *owner* (who ran `/setup`), so listing in a server would leak that user's private chats. Two independent guards: command rejects in guilds *and* the RPC only ever resolves via the user/DM path (never a server id). - **Resume backstop:** even if a pick were spoofed with a foreign `session_id`, `start_chat_turn` re-validates `get_chat_session(session_id, owner)` and returns `None` on a user-id mismatch — a foreign session can never be loaded. - **3-second ack:** the interaction is `defer()`'d immediately so the RPC + DB roundtrip stays inside Discord's interaction window; the picker is sent via `followup.send(view=...)`. (First version hit `404 Unknown interaction` without the defer.) - **Picker UI:** first interactive component in the bot — `discord.ui.View` + `discord.ui.Select`, ephemeral, 180s timeout. Callback delegates to `_resume_to_session` so the cache-set logic is unit-testable. ### Changes 🏗️ - `platform_linking/models.py` — `ChatSessionSummary` + `ListUserChatsResponse`. - `platform_linking/chat.py` — `list_user_chats` (DM-only owner resolution + scoped session listing). - `platform_linking/manager.py` — `@expose list_user_chats` + client method. - `copilot/bot/bot_backend.py` — `ChatSummary` dataclass + `list_user_chats` facade. - `copilot/bot/sessions.py` — `set_session` helper. - `copilot/bot/adapters/discord/commands.py` — `/resume` slash command, ephemeral dropdown picker, `_resume_to_session` helper, relative-time formatter, listed in `/help`. - Tests across `chat_test.py`, `sessions_test.py`, `commands_test.py`. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `/resume` in a DM lists my chats; picking one routes the next message to **that** chat on the platform (verified live) - [x] `/resume` in a server is rejected with the DM-only guidance (verified live) - [x] `/new` (the dependency on #13199) still works (verified live) - [x] Full `copilot/bot` + `platform_linking` suites green, `black` + `ruff` + `pyright` clean - [x] DM-only is enforced in two places: the command guard and the RPC owner-resolution path - [x] Slim response model — no `user_id`/credentials cross the bot ↔ manager wire
B
Bently committed
5ac3fe29a452fd8e36e72f3ed38efa3dafd057a1
Parent: d8d5d5f
Committed by GitHub <noreply@github.com>
on 5/27/2026, 2:53:24 PM