feat(telegram): ADMIN_USER_ID may /impersonate another user

While on, the admin's updates are routed as the target user's (allow-list,
chat, workspace, session, settings, jobs) and the target chat's output goes
to the admin's chat. The target's own updates are dropped with a notice that
an admin is working with their account. In memory; /impersonate 0 stops.
This commit is contained in:
EugeneTes
2026-10-05 07:24:07 +00:00
parent 64ea6f7fa1
commit 92a0061fdd
20 changed files with 398 additions and 38 deletions

View File

@@ -68,6 +68,7 @@ The compose file maps them into the container.
|---|---|---|---|
| `TELEGRAM_BOT_TOKEN` | — | Required. The token from @BotFather | No, and redacted from tool output |
| `TELEGRAM_ALLOWED_USER_IDS` | — | Required. Comma-separated Telegram user ids; the first one is the owner (the `🟢 Online.` message, Claude re-login prompts) | Yes |
| `ADMIN_USER_ID` | — | Telegram user id that may `/impersonate` other users ([telegram-bot.md](telegram-bot.md#impersonation)) | Yes |
| `TELEGRAM_MODEL` | `claude-code/claude-opus-5-5` | Model every chat uses; compose passes it as `Telegram__Model` | Yes, as `Telegram__Model` |
| `TZ` | `UTC` | The container's time zone | The agent's `bash` gets the chat's zone as `TZ` instead |
| `LOG_LEVEL` | `Information` | Passed as `Logging__LogLevel__Default` | Yes |

View File

@@ -19,6 +19,7 @@ Chat with the agent through a Telegram bot. A new host project in this repo, run
| Tools | All built-in tools; each chat works in its own directory `<WorkspaceRoot>/<chatId>` |
| Access | Mandatory user-id allow-list; empty list = refuse to start; other users are ignored silently |
| Chats | Private chats only |
| Impersonation | Optional `AdminUserId` (`ADMIN_USER_ID`) may `/impersonate <user_id>` (`0` stops, in memory). The admin's updates are then routed as the target's (allow-list, chat, workspace, session, settings, jobs) and the target chat's output (replies, questions, files, scheduled deliveries) goes to the admin's chat. The target's own updates are dropped with a "an admin is working with your account" notice |
Out of scope: webhooks, Docker/Coolify (see `2026-09-26-docker-deployment-design.md`), voice and documents, group chats, several bot tokens, a custom polling loop.
@@ -30,6 +31,7 @@ Out of scope: webhooks, Docker/Coolify (see `2026-09-26-docker-deployment-design
- `AllowedUserIds`, required, non-empty (`Telegram__AllowedUserIds__0=123`, or `TELEGRAM_ALLOWED_USER_IDS=1,2`).
- `WorkspaceRoot`, default `~/.volcanic/telegram-workspaces`.
- `Model`, default `claude-code/claude-opus-5-5`.
- `AdminUserId` (also `ADMIN_USER_ID`), optional, positive: the user who may `/impersonate`.
At start the host logs whether the `claude` CLI was found; a missing CLI is a warning (the doctor command explains).

View File

@@ -29,6 +29,7 @@ settings use the `GoogleCalendar` section, the scheduler the `Scheduler` section
|---|---|---|
| `TELEGRAM_BOT_TOKEN` / `Telegram:BotToken` | — | Required |
| `TELEGRAM_ALLOWED_USER_IDS` / `Telegram:AllowedUserIds` | — | Required, comma separated. The bot refuses to start without it |
| `ADMIN_USER_ID` / `Telegram:AdminUserId` | — (off) | The Telegram user id that may use `/impersonate` (see [Impersonation](#impersonation)). Need not be on the allow-list. Must be positive |
| `Telegram:WorkspaceRoot` | `~/.volcanic/telegram-workspaces` | Chat `<id>` works in `<root>/<id>`; put `AGENTS.md`, `PERSONA.md`, `MEMORY.md` there |
| `Telegram:Model` | `claude-code/claude-opus-5-5` | Any `provider/model` the Claude Code provider serves |
| `Telegram:EditInterval` | `00:00:01.5` | Minimum time between edits of the "Working..." status message |
@@ -205,6 +206,22 @@ the current format, and keeps the original next to it as `jobs.json.secretary-<t
Copy the workspace's `scripts/` into the owner's workspace too: watcher commands such as `python scripts/check.py` run
from there. A one-off that is already due fires on the first start.
### Impersonation
The admin (`ADMIN_USER_ID`) can act as another user with `/impersonate <user_id>`; `/impersonate 0` (or the admin's
own id) stops. The command shows up in the admin's command menu only, and from anyone else it is an ordinary unknown
command passed to the agent.
While it is on, the bot treats the admin's messages, commands, button presses and answers exactly as the target user's:
the allow-list check (impersonating someone outside it means the bot ignores the admin until they stop), the chat, its
workspace, session, time zone, jobs and calendar. Everything the target's chat sends goes to the admin's chat instead:
replies, questions, files, notices and scheduled deliveries, including runs started by the target user's own messages.
Meanwhile the target user is blocked: whatever they send (messages, commands, button presses) is dropped, and the bot
answers "🔒 An admin is working with your account right now. Please try again later." The admin's own chat (their own jobs, for
example) keeps working as before.
Impersonation is kept in memory only: a restart ends it.
### Google Calendar
Each chat connects its own Google account with `/calendar_connect`, and the agent's `calendar` tool only uses that
@@ -215,7 +232,8 @@ storage and time zones are in [google-calendar.md](google-calendar.md).
## Security
The agent has the built-in tools, including `bash`, and runs them as the bot's OS user. Only allow-listed user ids
are served (everyone else is ignored and logged), and only in private chats. Run it in the container image
are served (everyone else is ignored and logged), and only in private chats. `ADMIN_USER_ID` can act as any
allow-listed user with `/impersonate`, so it gets that user's workspace, sessions, jobs and connected calendar. Run it in the container image
([docker.md](docker.md)), which is the recommended setup, or at least as a dedicated user, and treat the workspace root
as writable by anyone on the allow-list.