> ## Documentation Index
> Fetch the complete documentation index at: https://dev-doc.dualmindlab.tech/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are the DualMind developer and admin docs. The API base URL is https://api.dualmindlab.tech and the local API runs on http://localhost:5079.
> Never send POST, PUT, PATCH or DELETE requests to the production API while developing; use a local API and a development Supabase project.
> Environment variables are documented by name only. Never ask for or print secret values.
> Admin routes under /api/admin require a Supabase access token for a user whose public.users.role is admin. The PUT routes for users, models, providers and threads write every field, so send the full record; PUT /api/admin/keys/{keyId} writes only the fields you send (sending api_key replaces the key).

# Telegram bot

> The DualMind Telegram bot: how it is hosted inside the API, how players sign in, and how battles and votes flow.

The Telegram bot lets players run blind battles and vote from a chat. It runs **inside the API process** as a hosted service (`Bot/`), built on Telegram.Bot 22, and calls the API's own public routes with the player's token.

## Commands

| Command | What it does |
| :- | :- |
| `/start` | Opens the main menu and the sign-in flow |
| `/battle` | Starts a blind battle with the next message as the prompt |
| `/stats` | Shows the top of the leaderboard |
| `/help` | Lists the commands |
| `/cancel` | Cancels the current action |

## How a battle works

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sequenceDiagram
    participant T as Telegram user
    participant TG as Telegram
    participant W as dualmind-api Worker
    participant H as TelegramUpdateHandler
    participant API as API routes

    T->>TG: /battle, then a prompt
    TG->>W: POST /api/telegram/webhook (secret token header)
    W->>H: forward to the container
    H->>API: POST api/arena/dualchat (player's token)
    API-->>H: two answers + comparisonId
    H->>TG: both answers, with vote buttons
    T->>TG: taps a vote button
    TG->>W: callback query
    H->>API: POST api/arena/model-vote
    H->>TG: "vote recorded", names revealed
```

* A per-chat cooldown (`BattleCooldownSeconds`, 15 s) stops battle spam.
* While a battle runs the bot sends a typing heartbeat every `BattleHeartbeatSeconds` (4 s) and a "still working" note after `SoftTimeoutSeconds` (30 s).
* Bot calls to the API time out after `ApiTimeoutSeconds` (75 s).
* Votes that arrive after the battle state expired get a friendly "vote expired" reply.

## Sign-in and sessions

Players sign in with their DualMind email and password inside the chat. The bot exchanges them for a Supabase session and stores it in `telegram_sessions` (keyed by `telegram_chat_id`), with the access and refresh tokens **encrypted with `APP_SECRET`**. It refreshes expired sessions itself.

## Delivery mode and configuration

The bot is enabled only when both a bot token and `Telegram:ApiBaseUrl` are set. Options bind from the `Telegram` configuration section (environment variables use `Telegram__Name`).

| Option | Default | Notes |
| :- | :- | :- |
| `BotToken` | none | From a Worker secret |
| `ApiBaseUrl` | none | `https://api.dualmindlab.tech` in production (`wrangler.jsonc`) |
| `DeliveryMode` | `Auto` | `Webhook` in production. `Auto` uses a webhook when `ApiBaseUrl` is a public URL, otherwise long polling (handy locally) |
| `WebhookPath` | `/api/telegram/webhook` | |
| `WebhookSecretToken` | none | Checked against the `X-Telegram-Bot-Api-Secret-Token` header |
| `SignupUrl` | set in `appsettings.json` | Link shown to players without an account. It should point at `https://arena.dualmindlab.tech`; the value in `appsettings.json` still uses an old placeholder domain, so override it with `Telegram__SignupUrl` |
| `BattleCooldownSeconds` / `BattleHeartbeatSeconds` / `SoftTimeoutSeconds` / `ApiTimeoutSeconds` | 15 / 4 / 30 / 75 | |

In production the API Worker also checks Telegram's `getWebhookInfo` on the first request after each deploy and calls `setWebhook` if the URL doesn't point at `/api/telegram/webhook`.

## Run it locally

Set a **separate test bot's** token and leave `DeliveryMode` on `Auto` with a localhost `ApiBaseUrl`: the bot falls back to long polling, so you don't need a public URL. Never point a local API at the production bot token; Telegram allows only one consumer per bot.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.