feat(telegram): self-contained pairing for chat ownership verification

BotFather issues bot tokens with no user binding, so anyone who guesses the
bot's username can DM it and get registered as a channel. Pairing closes that
gap: setup issues a one-time 4-digit code, the operator echoes it back from
the chat they want to register, and the inbound interceptor binds
admin_user_id before the message reaches the router.

- src/channels/telegram-pairing.ts: JSON-backed store with createPairing,
  tryConsume, getStatus, waitForPairing (fs.watch + poll fallback)
- src/channels/telegram.ts: wraps bridge.setup with an onInbound interceptor
  that consumes pairing codes and upserts messaging_groups
- setup/pair-telegram.ts: CLI step issues a code and waits up to 5 min for
  the operator to echo it back, emitting PLATFORM_ID/IS_GROUP/ADMIN_USER_ID
- Skill docs: /setup reorders mounts -> service -> wire (pairing needs a
  live polling adapter); /manage-channels and /add-telegram-v2 use pairing
  instead of asking the user to discover chat IDs

All other channels still bind admin via install-time identity (OAuth/QR/token);
pairing is Telegram-only. The bridge, router, and other adapters are untouched.
This commit is contained in:
Koshkoshinsk
2026-04-13 12:27:02 +00:00
parent af13c23a5a
commit 2017589683
8 changed files with 679 additions and 23 deletions
+1 -1
View File
@@ -68,7 +68,7 @@ Otherwise, run `/manage-channels` to wire this channel to an agent group.
- **type**: `telegram`
- **terminology**: Telegram calls them "groups" and "chats." A "group" has multiple members; a "chat" is a 1:1 conversation with the bot.
- **how-to-find-id**: Send a message in the group/chat, then visit `https://api.telegram.org/bot<TOKEN>/getUpdates` the `chat.id` field is the platform ID. Group IDs are negative numbers.
- **how-to-find-id**: Do NOT ask the user for a chat ID. Telegram registration uses pairing — run `npx tsx setup/index.ts --step pair-telegram -- --intent <main|wire-to:folder|new-agent:folder>`, show the user the 4-digit `CODE` from the `PAIR_TELEGRAM_ISSUED` block, and tell them to send `@<botname> CODE` from the chat they want to register (DM the bot for `main`, post in the group otherwise). The step waits up to 5 minutes and emits a `PAIR_TELEGRAM` block with `PLATFORM_ID`, `IS_GROUP`, and `ADMIN_USER_ID` once the user echoes the code. The service must be running for this to work (the polling adapter is what observes the code).
- **supports-threads**: no
- **typical-use**: Interactive chat — direct messages or small groups
- **default-isolation**: Same agent group if you're the only participant across multiple chats. Separate agent group if different people are in different groups.
+4 -6
View File
@@ -17,8 +17,8 @@ Categorize channels as: **wired** (has DB entities), **configured but unwired**
1. Ask the assistant name (default: project name or "Andy")
2. Ask which channel is the primary/admin channel
3. Ask for the platform ID — read the channel's SKILL.md `## Channel Info` > `how-to-find-id` to guide them
4. Register:
3. **Telegram special case:** if the chosen channel is `telegram`, do not ask for an ID. Run `npx tsx setup/index.ts --step pair-telegram -- --intent main`, show the user the 4-digit CODE from the `PAIR_TELEGRAM_ISSUED` block, and tell them to DM the bot with `@<botname> CODE` from the chat they want as their main. Wait for the `PAIR_TELEGRAM` block — `PLATFORM_ID`, `IS_GROUP`, `ADMIN_USER_ID` come back from there. Skip step 4 of this list (the messaging group is already created with admin binding); instead run only the agent-group + wiring portion via `setup --step register` with the returned `PLATFORM_ID`.
4. Otherwise (non-Telegram), ask for the platform ID — read the channel's SKILL.md `## Channel Info` > `how-to-find-id` to guide them, then register:
```bash
npx tsx setup/index.ts --step register -- \
@@ -64,10 +64,8 @@ For separate agents, also ask for a folder name and optionally a different assis
When adding another group/chat on an already-configured platform (e.g. a second Telegram group):
1. Read the channel's SKILL.md `## Channel Info` for terminology and how-to-find-id
2. Ask for the new group/chat ID
3. Ask the isolation question
4. Register — no package or credential changes needed
1. **Telegram:** ask the isolation question first to determine intent (`wire-to:<folder>` for an existing agent, `new-agent:<folder>` for a fresh one). Run `npx tsx setup/index.ts --step pair-telegram -- --intent <intent>`, show the CODE, and tell the user to post `@<botname> CODE` in the target group (or DM the bot for a private chat). Wait for the `PAIR_TELEGRAM` block, then run `setup --step register` with the returned `PLATFORM_ID` and the chosen folder/session-mode. The messaging group row is already created with `admin_user_id` set — `register` only needs to add the wiring.
2. **Other channels:** read the channel's SKILL.md `## Channel Info` for terminology and how-to-find-id. Ask for the new group/chat ID, ask the isolation question, then register. No package or credential changes needed.
## Change Wiring
+14 -12
View File
@@ -288,17 +288,6 @@ npm install && npm run build
If the build fails, read the error output and fix it (usually a missing dependency). Then continue to step 5a.
## 5a. Wire Channels to Agent Groups
Invoke `/manage-channels` to wire the installed channels to agent groups. This step:
1. Creates the agent group(s) and assigns a name to the assistant
2. Asks for each channel's platform-specific ID (guided by channel-specific instructions)
3. Decides the isolation level — whether channels share an agent, session, or are fully separate
The `/manage-channels` skill reads each channel's `## Channel Info` section from its SKILL.md for platform-specific guidance (terminology, how to find IDs, recommended isolation).
**This step is required.** Without it, channels are installed but not wired — messages will be silently dropped because the router has no agent group to route to.
## 6. Mount Allowlist
AskUserQuestion: Agent access to external directories?
@@ -336,6 +325,19 @@ Replace `USERNAME` with the actual username (from `whoami`). Run the two `sudo`
- Linux: check `systemctl --user status nanoclaw`.
- Re-run the service step after fixing.
## 7a. Wire Channels to Agent Groups
The service is now running, so polling-based adapters (Telegram) can observe inbound messages — required for pairing.
Invoke `/manage-channels` to wire the installed channels to agent groups. This step:
1. Creates the agent group(s) and assigns a name to the assistant
2. Resolves each channel's platform-specific ID (Telegram via pairing code; other channels via the platform's own ID lookup)
3. Decides the isolation level — whether channels share an agent, session, or are fully separate
The `/manage-channels` skill reads each channel's `## Channel Info` section from its SKILL.md for platform-specific guidance (terminology, how to find IDs, recommended isolation).
**This step is required.** Without it, channels are installed but not wired — messages will be silently dropped because the router has no agent group to route to.
## 8. Verify
Run `npx tsx setup/index.ts --step verify` and parse the status block.
@@ -345,7 +347,7 @@ Run `npx tsx setup/index.ts --step verify` and parse the status block.
- SERVICE=not_found → re-run step 7
- CREDENTIALS=missing → re-run step 4 (Docker: check `onecli secrets list`; Apple Container: check `.env` for credentials)
- CHANNEL_AUTH shows `not_found` for any channel → re-invoke that channel's skill (e.g. `/add-telegram`)
- REGISTERED_GROUPS=0 → re-invoke `/manage-channels` from step 5a
- REGISTERED_GROUPS=0 → re-invoke `/manage-channels` from step 7a
- MOUNT_ALLOWLIST=missing → `npx tsx setup/index.ts --step mounts -- --empty`
Tell user to test: send a message in their registered chat. Show: `tail -f logs/nanoclaw.log`