# 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.