SIGN IN SIGN UP

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.

0 0 73 Python

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