# pi-telegram-group-topic

Control several [pi](https://pi.dev/) sessions from one Telegram bot.

This pi extension lets a Telegram forum group act like a control room for pi: bind one topic to one live pi session, bind another topic to another session, and keep the normal Telegram DM bridge for quick one-on-one use. It supports streaming replies, queued turns, attachments, generated files, inline buttons, `/status`, `/model`, `/compact`, `/stop`, and topic binding through `/bind` codes.

It started as a fork of Mario Zechner's [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram), then grew into a multi-topic router for people who keep several pi sessions open at the same time.

## Good for

- running several pi sessions from one Telegram group;
- keeping each project/session in its own Telegram forum topic;
- checking model, token, cost, and queue state from Telegram;
- sending screenshots, documents, and generated files between Telegram and pi;
- mirroring local pi prompts/replies back to a bound topic when you want a shared log.

## What it adds

- Multi-topic group routing: one Telegram forum topic maps to one live pi session.
- A single Telegram poller: one lock-owning pi process consumes `getUpdates`; other sessions receive routed updates through local inboxes.
- Simple Telegram commands: `/status`, `/model`, `/compact`, `/stop`, `/bind`, `/help`, and `/start`.
- Pi-side commands under `/tg-*`, so they do not collide with Telegram command names.
- Inline status and model menus from Telegram, including inside forum topics.
- Streaming Telegram previews while pi is generating a reply.
- Queue controls for busy sessions. `/stop` aborts the active run and clears queued Telegram turns.
- Media and file handling for images, albums, documents, and generated artifacts.
- Optional local chat broadcast from pi back to the bound Telegram topic.
- Usage limits in `/status` when `pi-usage-bars` is installed; otherwise it shows token/cost/context data.

## Install

From npm:

```bash
pi install npm:pi-telegram-group-topic
```

From GitHub:

```bash
pi install git:github.com/AlekseiSeleznev/pi-telegram-group-topic
```

Try it for one pi session without installing globally:

```bash
pi -e npm:pi-telegram-group-topic
```

## Create a Telegram bot

1. Open [@BotFather](https://t.me/BotFather).
2. Run `/newbot`.
3. Choose a bot name and username.
4. Copy the bot token.

For group-topic routing, also add the bot to a Telegram group with **Topics** enabled. Give the bot permission to read and send messages in the group.

## Configure pi

Start pi and run:

```text
/tg-setup
```

Paste your Telegram bot token when prompted.

The extension stores bot configuration in:

```text
~/.pi/agent/telegram.json
```

If a token already exists there, the setup prompt shows it as the default. If no saved token exists, the setup prompt can prefill from these environment variables:

- `TELEGRAM_BOT_TOKEN`
- `TELEGRAM_BOT_KEY`
- `TELEGRAM_TOKEN`
- `TELEGRAM_KEY`

The config file is written with private `0600` permissions.

## Connect a pi session

In the pi session that should own Telegram polling, run:

```text
/tg-connect
```

Only one live pi process should poll the Telegram Bot API for a bot token. `pi-telegram-group-topic` stores polling ownership in:

```text
~/.pi/agent/locks.json
```

If another live pi process already owns the lock, `/tg-connect` asks whether to move ownership to the current session.

To stop polling from the current session:

```text
/tg-disconnect
```

To inspect bridge state from pi:

```text
/tg-status
```

## Pair your Telegram account

After configuring the token and running `/tg-connect`:

1. Open a DM with your bot.
2. Send `/start`.

The first Telegram user to pair becomes the allowed user. Messages from other users are ignored.

## Basic DM usage

Send a message to the bot DM. The message is forwarded into pi with a `[telegram]` prefix.

Examples:

```text
summarize this file
```

```text
write a shell script and send it back to me
```

```text
explain this screenshot
```

## Telegram commands

Use these commands in Telegram DMs or in a bound group topic:

| Command | What it does |
| --- | --- |
| `/start` | Pair the first allowed Telegram user and show help. |
| `/help` | Show Telegram help. |
| `/status` | Show model, usage, cost/context or usage-bar limits, plus inline controls. |
| `/model` | Open the interactive model selector. |
| `/compact` | Compact the current pi session when it is idle. |
| `/stop` | Abort the current pi run and clear queued Telegram turns. |
| `/bind <code>` | Bind the current Telegram chat/topic to the pi session that generated the code. |

Telegram-side commands intentionally keep their short names. The longer `/tg-*` commands are for pi only.

## Pi commands

Run these commands inside pi, not Telegram:

| Command | What it does |
| --- | --- |
| `/tg-setup` | Configure or update the Telegram bot token. |
| `/tg-status` | Show bridge diagnostics, polling state, queue state, and recent redacted runtime/API events. |
| `/tg-connect` | Start Telegram polling in this pi session and acquire or move the singleton lock. |
| `/tg-disconnect` | Stop polling in this pi session and release the singleton lock. |
| `/tg-connect-topic <chatId> [threadId]` | Manually bind this pi session to a Telegram chat or forum topic. |
| `/tg-bind-code` | Generate a temporary code that can be used with Telegram `/bind <code>`. |
| `/tg-topic-unbind` | Remove this pi session's Telegram topic binding. |
| `/tg-topic-status` | Show this pi session's current topic binding. |
| `/tg-broadcast-on` | Mirror local pi prompts and final assistant replies to the bound Telegram topic. |
| `/tg-broadcast-off` | Disable local chat mirroring. |
| `/tg-broadcast-status` | Show local chat mirroring status. |

## Multi-topic group routing

The main feature of this fork is routing different Telegram forum topics to different live pi sessions.

### Why this exists

The Telegram Bot API allows only one `getUpdates` consumer per bot token. Running one Telegram extension per pi session would conflict. This extension solves that by keeping a single active poller and routing updates locally.

### How it works

- The lock-owning pi session runs the Telegram poller.
- Every live pi session registers itself in:

```text
~/.pi/agent/pi-telegram-sessions.json
```

- Each target session has a UTF-8-safe local inbox under:

```text
~/.pi/agent/pi-telegram-router/inbox/
```

- The poller maps `(chat_id, message_thread_id)` to the registered target session.
- The target session consumes the routed update through the normal Telegram pipeline: commands, media downloads, queueing, rendering, buttons, and replies.

### Bind a topic with a code

In the target pi session, run:

```text
/tg-bind-code
```

pi will show a code. In the Telegram topic that should talk to that session, send:

```text
/bind CODE
```

After that, messages and Telegram-side commands in that topic go to the bound pi session.

### Manually bind a topic

If you already know the Telegram chat id and topic thread id, run this inside the target pi session:

```text
/tg-connect-topic <chatId> <threadId>
```

For a non-topic chat, omit `threadId`:

```text
/tg-connect-topic <chatId>
```

### Check or remove a binding

```text
/tg-topic-status
```

```text
/tg-topic-unbind
```

## Local broadcast to Telegram

By default, Telegram-originated messages get Telegram replies, but ordinary local pi prompts stay local.

To mirror local pi prompts and final assistant replies to the currently bound Telegram topic:

```text
/tg-broadcast-on
```

To stop mirroring:

```text
/tg-broadcast-off
```

To inspect the setting:

```text
/tg-broadcast-status
```

The footer in pi uses compact indicators:

```text
tg: poll 🟢 · bind 🟢 · brc 🟡
```

- `poll 🟢` / `poll 🟡`: this pi session owns Telegram polling / does not own polling.
- `bind 🟢` / `bind 🟡`: this pi session is bound to a Telegram chat or topic / is not bound.
- `brc 🟢` / `brc 🟡`: local chat broadcast to Telegram is on / off.

A normal routed target session can show `poll 🟡 · bind 🟢`: it does not call `getUpdates` itself, but Telegram messages and menu callbacks still reach it through the router.

## Status and usage limits

Telegram `/status` shows model and context information. If [`pi-usage-bars`](https://pi.dev/packages?name=usage-bars) is installed and emits usage data, `/status` prefers limit information such as:

```text
Limits: S 74% left W 37% left
Reset: S 2h W 4d
Context: 22.9%/272k
```

If usage-bar data is unavailable, it falls back to token usage, cost, and context:

```text
Usage: ↑1.1M ↓98k R37M
Cost: $26.939 (sub)
Context: 22.9%/272k
```

## Media and attachments

You can send images, albums, voice messages, and files from Telegram. The extension downloads inbound files to:

```text
~/.pi/agent/tmp/telegram
```

It includes local file paths in the prompt and forwards supported images as image inputs to pi.

When pi needs to send generated artifacts back to Telegram, it should call the provided `telegram_attach` tool with local file paths. The extension sends those files with the next Telegram reply.

## Queueing and controls

If pi is busy, new Telegram turns are queued and processed later.

- Normal messages enter the default prompt queue.
- Important control actions such as model switching use a higher-priority control lane.
- `/stop` aborts the active pi run and clears queued Telegram turns.
- Reactions can be used for queue management where supported by Telegram updates.

## Streaming replies

Assistant replies are streamed back to Telegram as previews while pi is generating.

The extension first tries Telegram draft streaming with `sendMessageDraft`. If that is not available for the bot/client combination, it falls back to sending and editing normal Telegram messages.

Long replies are split below Telegram's message-size limit.

## Files and runtime state

Important local files:

| Path | Purpose |
| --- | --- |
| `~/.pi/agent/telegram.json` | Bot token and allowed Telegram user configuration. |
| `~/.pi/agent/locks.json` | Singleton polling ownership. |
| `~/.pi/agent/pi-telegram-sessions.json` | Live pi session registry and topic bindings. |
| `~/.pi/agent/pi-telegram-router/inbox/` | Per-session routed update inboxes. |
| `~/.pi/agent/tmp/telegram/` | Downloaded Telegram files and media. |

## Troubleshooting

### `/status` or `/model` replies appear in General instead of the topic

Make sure the live pi process has been fully restarted after updating the extension. A pi `/reload` may not replace old in-memory Telegram hooks. Stop old pi processes, start pi again, run `/tg-connect`, and retry.

### Telegram says another poller is active

Only one process can call `getUpdates` for a bot token. Run `/tg-connect` in the intended router session and confirm takeover if prompted. Stop stale pi processes if necessary.

### A topic says it is not bound

Generate a new code in the target pi session:

```text
/tg-bind-code
```

Then send the shown command in the Telegram topic:

```text
/bind CODE
```

### An old experimental topic broker is installed

This project does not use the temporary `telegram-topic-broker` experiment. If it exists under `~/.pi/agent/extensions/telegram-topic-broker`, disable or remove it before using this extension.

## Development

```bash
npm install
npm run validate
```

Validation runs TypeScript typechecking, tests, `npm audit`, and a dry-run package check.

## Acknowledgements

This project is based on [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram), the original Telegram DM bridge for pi. The current fork keeps the original spirit and core bridge behavior while adding multi-session Telegram forum-topic routing, topic-aware command menus, usage-limit status integration, broadcast controls, compact status indicators, and additional tests/docs.

## License

MIT. See [LICENSE](LICENSE).
