Files

99 lines
4.1 KiB
Markdown
Raw Permalink Normal View History

2026-03-27 16:55:22 -04:00
# AGENTS.md
Instructions for AI agents working on this repository.
## Project overview
`opencode-dispatch` is a Telegram bot that acts as a secure remote bridge to a locally-running `opencode` instance. It forwards messages from a single authorized Telegram chat to the opencode REST API and relays the responses back.
Single entry point: `bot.py`. Python only.
## Architecture
```
Telegram (user) ──► bot.py ──► opencode REST API (opencode serve)
◄──────────────────────────────
```
**Concurrency model:**
- `python-telegram-bot` runs an asyncio event loop
- `send_to_opencode()` is synchronous (blocking HTTP, up to 1200s timeout) and is always called via `loop.run_in_executor()` to avoid blocking the event loop
- A `queue.Queue` serializes requests when a message arrives while one is already processing
- A `threading.Lock` (`processing_lock`) guards `is_processing` and `current_task`
**opencode API endpoints used:**
| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/global/health` | Health check (`/status` command) |
| `GET` | `/session` | List existing sessions |
| `POST` | `/session` | Create a new session |
| `POST` | `/session/{id}/message` | Send a message and get the response |
## Environment variables
Defined in `.env` (copy from `.env.example`).
| Variable | Required | Description |
|----------|----------|-------------|
| `TELEGRAM_BOT_TOKEN` | **Yes** | Bot token from @BotFather |
| `TELEGRAM_ALLOWED_CHAT_ID` | **Yes** | Telegram chat ID to allow. Bot refuses to start if unset. |
| `OPENCODE_API_URL` | No | Default: `http://127.0.0.1:5050` |
| `OPENCODE_SERVER_PASSWORD` | No | If set on the opencode server, must match |
> **Note:** `OPENCODE_SERVER_PASSWORD` is read from the env but not yet forwarded in HTTP requests. If you implement password support, add it as a header or query param to `get_session()` and `send_to_opencode()`.
## Security invariants — do not break
1. **`ALLOWED_CHAT_ID` is enforced.** `main()` exits if `TELEGRAM_ALLOWED_CHAT_ID` is not set.
2. **`is_authorized()` is called at the top of every handler** — including all command handlers (`/start`, `/help`, `/status`, `/working`, `/clear`) and all message handlers. Unauthorized requests are silently dropped (no reply).
3. **No internal info is leaked to Telegram.** Error messages sent to the user are generic strings. Detailed errors go to `logger` only.
4. **No user-visible stack traces.** Use `logger.exception()` server-side, return a static string to the user.
## Adding a new bot command
1. Write an `async def my_command(update, context)` function.
2. Call `if not is_authorized(update): return` as the first line.
3. Register it in `main()` with `app.add_handler(CommandHandler("my_command", my_command))`.
4. Add it to the help text in `help_command()`.
## Adding a new message type handler (e.g. video)
Same pattern as `handle_voice`, `handle_document`, `handle_photo`:
1. Implement the handler with an `is_authorized` guard.
2. Register with `app.add_handler(MessageHandler(filters.VIDEO, handle_video))` in `main()`.
## Running locally
```bash
# Install dependencies
pip install -r requirements.txt
# Configure
cp .env.example .env
# Edit .env: fill TELEGRAM_BOT_TOKEN and TELEGRAM_ALLOWED_CHAT_ID
# Start opencode in your target project folder
cd ~/your-project
opencode serve --port 5050
# Run the bot
python bot.py
```
## Dependencies
```
python-telegram-bot==21.6 # async Telegram bot framework
requests==2.32.3 # synchronous HTTP client for opencode API
python-dotenv==1.0.1 # .env file loading
```
No linting or test runner is currently configured. The `.gitignore` includes `.ruff_cache/` and `.pytest_cache/` anticipating future setup.
## What is not implemented yet
- `OPENCODE_SERVER_PASSWORD` forwarding in HTTP requests
- Voice, photo, and document handling (handlers exist but reply "not supported")
- Queued message processing: messages added to the queue while the bot is busy are stored but never drained — the queue worker (`process_queue`) was removed. Queued items are currently orphaned.