A Telegram bot that orchestrates multiple OpenCode instances through forum topics. Each forum topic in a Telegram supergroup gets its own dedicated OpenCode instance, enabling multi-user/multi-project AI assistance.
- Forum Topic to OpenCode Instance: Each topic gets a dedicated OpenCode session
- Real-time Streaming: SSE events from OpenCode are streamed to Telegram as editable messages
- Session Discovery: Connect to any running OpenCode instance on your machine
- Instance Lifecycle Management: Auto-start, health checks, crash recovery, idle timeout
- Persistent State: SQLite databases track topic mappings and instance state across restarts
- Permission Handling: Approve/deny dangerous operations via inline buttons
- Quick Start
- Running with Docker
- Usage
- Architecture
- Configuration
- API Reference
- Development
- Contributing
- License
- Open Telegram and message @BotFather
- Send
/newbotand follow the prompts - Copy the bot token (looks like
123456789:ABCdefGHIjklMNOpqrsTUVwxyz)
- Create a new Telegram group
- Convert it to a supergroup (Settings → Group Type → Supergroup)
- Enable Topics (Settings → Topics → Enable)
- Add your bot as an admin with permissions to manage topics
The chat ID for supergroups starts with -100. You can find it by:
- Adding @RawDataBot to your group temporarily
- It will show the chat ID in its message
- Remove the bot after getting the ID
# Clone the repository
git clone https://github.com/huynle/opencode-telegram.git
cd opencode-telegram
# Install dependencies
bun install
# Configure environment
cp .env.example .env
# Edit .env with your bot token and chat ID# Development with hot reload
bun run dev
# Production
bun run startImportant: Running natively with Bun is recommended for full functionality. Docker has significant limitations for this project's use case.
The bot's session discovery feature uses ps and lsof to find OpenCode instances running on your machine. Docker containers have isolated process namespaces, meaning the bot cannot discover OpenCode sessions running on your host.
| Feature | Native (Bun) | Docker | Docker + --pid=host |
|---|---|---|---|
/new - create managed instances |
Works | Works | Works |
/sessions - discover host sessions |
Works | No | Linux only |
/connect - attach to discovered sessions |
Works | No | Linux only |
| External API registration | Works | Works | Works |
| Stream responses to Telegram | Works | Works | Works |
- You only need managed instances (created via
/newcommand) - You're on Linux and can use
--pid=host - You want to use the External API to manually register instances
docker build -t opencode-telegram .If you only use /new to create instances (no discovery of external sessions):
docker run -d --name opencode-telegram \
--network=host \
-v $(pwd)/data:/app/data \
-v ~/oc-bot:/root/oc-bot \
--env-file .env \
opencode-telegramVolume Mounts:
| Mount | Purpose |
|---|---|
./data:/app/data |
SQLite databases for persistent state |
~/oc-bot:/root/oc-bot |
Project directories created by /new command |
On Linux, you can share the host's process namespace to enable discovery:
docker run -d --name opencode-telegram \
--network=host \
--pid=host \
-v $(pwd)/data:/app/data \
-v ~/oc-bot:/root/oc-bot \
--env-file .env \
opencode-telegramWarning:
--pid=hostshares the host's process namespace with the container. The container can see all host processes.
Run the bot in Docker and manually register OpenCode instances via the API:
# Start the bot
docker run -d --name opencode-telegram \
--network=host \
-v $(pwd)/data:/app/data \
-v ~/oc-bot:/root/oc-bot \
--env-file .env \
opencode-telegram
# Register an OpenCode instance running on the host
curl -X POST http://localhost:4200/api/register \
-H "Content-Type: application/json" \
-d '{
"projectPath": "/path/to/project",
"projectName": "my-project",
"opencodePort": 4096,
"sessionId": "ses_abc123"
}'On macOS and Windows, Docker Desktop runs containers in a Linux VM:
--network=hostdoesn't provide true host networking--pid=hostis not available- Discovery will not work - use native Bun or the External API
version: '3.8'
services:
opencode-telegram:
build: .
container_name: opencode-telegram
network_mode: host
# Uncomment for discovery (Linux only):
# pid: host
volumes:
- ./data:/app/data
- ~/oc-bot:/root/oc-bot
env_file:
- .env
restart: unless-stoppeddocker compose up -d
docker compose logs -f# View logs
docker logs -f opencode-telegram
# Check container status
docker ps -a --filter name=opencode-telegram
# Stop the bot
docker stop opencode-telegram
# Remove container
docker rm opencode-telegram
# Rebuild after code changes
docker build -t opencode-telegram . && docker compose up -dThese commands work in the General topic of your supergroup:
| Command | Description |
|---|---|
/new <name> |
Create folder + topic + start OpenCode instance |
/sessions |
List all OpenCode sessions (managed + discovered) |
/connect <name> |
Connect to an existing session by name or ID |
/clear |
Clean up stale topic mappings |
/status |
Show orchestrator status |
/help |
Show context-aware help |
These commands work inside individual topic threads:
| Command | Description |
|---|---|
/session |
Show current topic's OpenCode session info |
/link <path> |
Link topic to existing project directory |
/stream |
Toggle real-time streaming on/off |
/disconnect |
Disconnect session and delete topic |
/help |
Show context-aware help |
The bot can discover any running OpenCode instance on your machine:
/sessions # Lists all sessions including discovered ones
/connect myproject # Connect to a discovered session by name
/connect ses_abc123 # Connect by session ID prefix
Discovered sessions show with a magnifying glass icon in /sessions output.
Note: Discovery requires the bot to run natively (not in Docker) or with
--pid=hoston Linux. See Running with Docker for details.
Topics follow the <project>-<session title> naming convention:
- On
/new <project>: Topic is created with just<project>name initially - After first message: Once OpenCode generates a session title, the topic is automatically renamed to
<project>-<session title> - On
/connect: If the session already has a title, the topic is created with<project>-<session title>immediately
┌─────────────────────────────────────────────────────────────────────┐
│ Telegram Supergroup (Forum) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Topic #1 │ │ Topic #2 │ │ Topic #3 │ ... │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼─────────────┼─────────────┼─────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Integration Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ grammY Bot │ │TopicManager │ │StreamHandler│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────┐
│ Instance Manager (Orchestrator) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Instance #1 │ │ Instance #2 │ │ Instance #3 │ ... │
│ │ Port 4100 │ │ Port 4101 │ │ Port 4102 │ │
│ │ opencode │ │ opencode │ │ opencode │ │
│ │ serve │ │ serve │ │ serve │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
src/
├── index.ts # Entry point
├── config.ts # Configuration from environment
├── integration.ts # Wires all components together
├── api-server.ts # External instance registration API
├── bot/
│ └── handlers/
│ └── forum.ts # Telegram message/command handlers
├── forum/
│ ├── topic-manager.ts # Topic → Session mapping logic
│ └── topic-store.ts # SQLite persistence for topic mappings
├── opencode/
│ ├── client.ts # OpenCode REST API client
│ ├── discovery.ts # Discover running OpenCode instances
│ ├── stream-handler.ts # SSE → Telegram message bridging
│ └── telegram-markdown.ts # Markdown conversion for Telegram
├── orchestrator/
│ ├── manager.ts # Manages multiple instances
│ ├── instance.ts # Single OpenCode instance lifecycle
│ ├── port-pool.ts # Port allocation
│ └── state-store.ts # SQLite persistence for instance state
└── types/
├── forum.ts # Forum/topic types
└── orchestrator.ts # Orchestrator types
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | - | Bot token from @BotFather |
TELEGRAM_CHAT_ID |
Yes | - | Supergroup ID (starts with -100) |
PROJECT_BASE_PATH |
No | ~/oc-bot |
Where topic directories are created |
OPENCODE_PATH |
No | opencode |
Path to opencode binary |
OPENCODE_MAX_INSTANCES |
No | 10 |
Max concurrent instances |
OPENCODE_PORT_START |
No | 4100 |
Starting port for instances |
OPENCODE_IDLE_TIMEOUT_MS |
No | 1800000 |
Idle timeout (30 min) |
API_PORT |
No | 4200 |
External API server port |
See .env.example for all available options.
The bot exposes an API on port 4200 for external OpenCode instances to register. This is useful when running the bot in Docker without process discovery.
# Register an external instance
curl -X POST http://localhost:4200/api/register \
-H "Content-Type: application/json" \
-d '{
"projectPath": "/path/to/project",
"projectName": "my-project",
"opencodePort": 4096,
"sessionId": "ses_abc123"
}'
# Unregister
curl -X POST http://localhost:4200/api/unregister \
-H "Content-Type: application/json" \
-d '{"projectPath": "/path/to/project"}'
# Check status
curl http://localhost:4200/api/status/$(echo -n "/path/to/project" | base64)
# List all instances
curl http://localhost:4200/api/instances
# Health check
curl http://localhost:4200/api/healthEach OpenCode instance exposes:
GET /global/health # Health check
GET /session # List sessions
POST /session # Create session
GET /session/:id/message # Get messages
POST /session/:id/message # Send message (sync)
POST /session/:id/prompt_async # Send message (async)
GET /event # SSE event stream
# Install dependencies
bun install
# Start with hot reload
bun run dev
# Type check
bun run typecheck
# Format code (if prettier configured)
bun run format- Event-driven: Orchestrator emits events, integration layer handles them
- State recovery: Both orchestrator and topic manager recover state on restart
- Graceful degradation: Errors are logged but don't crash the bot
- New bot commands: Add to
src/bot/handlers/forum.tsincreateForumCommands() - New SSE event handling: Modify
src/opencode/stream-handler.ts - New instance lifecycle events: Modify
src/orchestrator/instance.ts
Symptom: Instance crashes with "Failed to start server on port 4100"
Solution: The code auto-cleans ports before starting. For manual cleanup:
lsof -ti:4100 | xargs killSymptom: Multiple "Thinking..." or response messages
Cause: Multiple SSE subscriptions or improper error handling
Solution: Fixed in current version by cleaning up subscriptions on instance:ready
Symptom: SSE events received but not forwarded to Telegram
Solution: Check that the topic is properly linked with /session command
Symptom: /sessions only shows managed instances, not host OpenCode sessions
Cause: Docker containers have isolated process namespaces - ps and lsof can only see container processes
Solutions (in order of recommendation):
- Run natively:
bun run start(recommended) - Linux with
--pid=host: Shares host process namespace - External API: Manually register instances via
/api/register
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.