Files
Julien Cabillot be3edc0e14
perso/opencode-dispatch/pipeline/head This commit looks good
feat: import
2026-03-27 19:12:29 -04:00

4.1 KiB

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

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