/api/terminal-bridge/* is the broker that connects the agent terminal (in the desktop app) with the Telegram bot. The two are separate processes that cannot talk directly, so they exchange text and media through these endpoints.
The bridge is dormant by default. Nothing is queued or delivered unless a terminal session is armed (session) and a chat is linked (chat). The gate is active = session_enabled && chat_linked.
See the feature docs: Controlling the Terminal and Telegram Terminal Bridge.
Per-bot routing (bot_id)
State is keyed per bot_id so several bots can be bridged at once — one terminal each. "default" is the bot from the Default token; extra bots use their own id. Every endpoint accepts a bot_id (body field or query param) and defaults to "default". Each bot has its own armed session, chat link, queues, and media de-dupe — they never cross.
Arm / disarm a terminal session
POST /api/terminal-bridge/session
{
"session_id": "pty-…",
"board_id": "…",
"enabled": true,
"bot_id": "default"
}
Called by the desktop app when a bot is picked / dropped in the terminal’s bot menu. enabled: false disarms that bot and drains its queues. Returns the bridge status for that bot_id.
Link / unlink a chat
POST /api/terminal-bridge/chat
{
"chat_id": 123456789,
"enabled": true,
"bot_id": "default"
}
Called by a bot’s /terminal command (each bot sends its own bot_id). enabled: false unlinks and drains the queues. Returns status plus board_name so the bot can sync its selected board:
{
"bot_id": "default",
"session_enabled": true,
"chat_linked": true,
"board_id": "…",
"board_name": "My Board",
"active": true
}
Status
GET /api/terminal-bridge/status?bot_id=default
Returns { bot_id, session_enabled, chat_linked, board_id, active } for that bot. Used to re-sync the app’s bot-icon state and the bot’s status text.
Terminal → Telegram
POST /api/terminal-bridge/to-telegram { "kind": "text", "text": "…", "bot_id": "default" }
GET /api/terminal-bridge/to-telegram?wait=25&bot_id=default
The app POSTs batched, ANSI-stripped terminal output for a bot. That bot long-polls the GET (returns { "items": [] } on timeout) and delivers each item to its linked chat. Items are only queued while that bot’s bridge is active.
Agent → Telegram (send-media)
POST /api/terminal-bridge/send-media
{ "url": "/media/admin/{board_id}/file.jpg", "caption": "optional", "session_id": "pty-…" }
Agent-facing: push a board media file to the chat of the bot this terminal is connected to. Routes by session_id (the agent reads it from $GREENLIGHT_TERMINAL_SESSION_ID) — it is not broadcast to other bots. If session_id is omitted it falls back to the sole active bridge only when exactly one bot is live. Returns 409 if the calling terminal isn’t connected to any bot.
Telegram → terminal
POST /api/terminal-bridge/to-terminal { "kind": "text", "text": "…", "bot_id": "default" }
POST /api/terminal-bridge/to-terminal { "kind": "path", "url": "/media/…", "bot_id": "default" }
GET /api/terminal-bridge/to-terminal?wait=25&bot_id=default
A bot POSTs either a text message (typed into the PTY and submitted) or a media url (resolved to an absolute local file path the agent can open — a missing file returns 404). The app long-polls the GET for each armed bot and writes items into that bot’s armed terminal.
Loop guard
Media uploaded to the board from Telegram carries data.bridge_origin = "telegram". The board media hook skips those items so they are not echoed back to the chat.
Leave a Reply