Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,22 @@ Add modules only when they reduce real complexity. Prefer the smallest correct c
## Telegram Adapter

- Authorization middleware should ignore unauthorized users and avoid leaking project state.
- Group routing, known group metadata, DM configuration menus, and ephemeral group memory belong in `src/adapters/telegram/`; do not move Telegram chat IDs, topics, or inline menus into core.
- Reaction API calls are best-effort warnings and must not block prompt delivery.
- `replyAndRemember` stores bot replies for reaction feedback. Use it for bot messages that should be remembered.
- Inline callback data must use short bounded tokens, not raw long session IDs or permission IDs.
- Permission prompts must remain text-only, even when voice replies are enabled.
- Photo downloads must not expose bot tokens in persisted attachment URLs.
- Sticker cache and saved pack state must not persist bot tokens, raw download URLs, chat IDs, user IDs, or raw Telegram payloads.
- Group message memory is in-memory only and must not persist message text. Persistent group state may store settings and non-secret group metadata.
- Always clean up downloaded media files in `finally` or equivalent cleanup paths.
- Keep Telegram UX in the adapter; do not move Telegram reactions, message IDs, chat actions, or grammY types into core.

## Config And State

- Runtime config is discovered from project-local `.opencode-remote/config.json`, then global `~/.opencode-remote/config.json`.
- `telegram.botToken` and `telegram.allowedUserId` are required and must stay private.
- `telegram.botToken` is required and must stay private.
- At least one of `telegram.allowedUserIds` or `telegram.allowedChatIds` is required. `allowedUserIds` authorizes private human DMs; `allowedChatIds` authorizes every sender in those group chats, including bots.
- Project-local `.opencode-remote/` is ignored because `config.json` contains secrets.
- App state is non-secret SQLite data in the platform app-data directory; see `DEVELOPMENT.md` for exact paths.
- Telegram sticker pack state is non-secret adapter state in `telegram-stickers.db`; reusable visuals are disposable cache under `cache/stickers`.
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ This project follows Semantic Versioning.

## Unreleased

## [0.7.0] - 2026-05-28

### Added

- Added configured Telegram group chat allowlists with group-scoped routing, ephemeral context memory, and DM-based `/group` settings.
- Added per-group custom trigger phrases for routing text, captions, and voice transcripts to OpenCode.

### Fixed

- Fixed custom group triggers to match bounded words or phrases so short bot names such as `Рес` do not trigger on longer words such as `ресурси`.

## [0.6.1] - 2026-05-28

### Fixed

- Fixed Telegram prompt author context for messages sent by anonymous admins or on behalf of chats/channels by using Telegram `sender_chat` names when available.

## [0.6.0] - 2026-05-28

### Added
Expand Down
21 changes: 14 additions & 7 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ OpenCode Remote is currently a Telegram gateway for OpenCode with text, image, s

## Available Now

- Telegram private-chat gateway using grammY long polling.
- Single authorized Telegram user configured in `.opencode-remote/config.json`.
- Telegram private-chat and configured group gateway using grammY long polling.
- Optional authorized Telegram users for private-chat access configured in `.opencode-remote/config.json`.
- Optional allowed Telegram group chat IDs that authorize every sender in those groups, including other bots.
- Local or remote OpenCode server connection configured with `opencode.apiUrl`.
- Optional local OpenCode startup with `opencode.autoStart=true`.
- OpenCode session creation, listing, switching, prompt sending, and stop requests.
Expand All @@ -27,15 +28,19 @@ OpenCode Remote is currently a Telegram gateway for OpenCode with text, image, s
- `/new` creates and selects a new OpenCode session.
- `/sessions` lists recent OpenCode sessions and lets the user switch with inline buttons.
- `/stop` requests abort for the active OpenCode session.
- `/progress` shows or sets prompt activity visibility: `off`, `new`, `all`, or `verbose`.
- `/progress` shows or sets private-chat prompt activity visibility: `off`, `new`, `all`, or `verbose`.
- `/voice` shows and controls voice mode, lists voices by required short country/locale filter, sets the active Edge TTS voice, and sends a test voice note.
- `/stickers` saves, lists, and forgets sticker packs for future sticker replies.
- `/group` opens a private-chat management menu for known allowed groups. In groups, `/group` replies with a short DM-only notice.
- Per-group custom trigger phrases are managed from the DM `/group` menu.
- `/help` shows the available bot commands.
- The Telegram slash-command menu is refreshed on gateway startup.
- Non-command text from the authorized user is sent to OpenCode as a prompt.
- Forwarded Telegram text, photo, album, and voice prompts include safe original-author context when Telegram provides it, with a safe fallback to the authorized user.
- Non-command text from an authorized private user is sent to OpenCode as a prompt. In allowed groups, text, photo, voice, and sticker messages are sent to OpenCode only when group routing settings identify them as addressed to the bot.
- Custom group trigger phrases are plain text, case-insensitive, and match as bounded words or phrases anywhere in text, captions, and voice transcripts.
- Allowed groups keep bounded in-memory recent context while the gateway is running. Routed group prompts include capped recent context, but passive messages are not sent to OpenCode by themselves.
- Telegram text, photo, album, voice, and sticker prompts include safe author context, including forwarded original authors and messages sent by anonymous admins or on behalf of chats/channels when Telegram provides usable names.
- The bot shows Telegram typing activity while a prompt is running.
- The bot can show an editable `Activity` message with OpenCode tools and skills used during a prompt.
- In private chats, the bot can show an editable `Activity` message with OpenCode tools and skills used during a prompt. Group chats always suppress this activity message.
- OpenCode permission requests are sent as text with `Allow once`, `Always allow`, and `Deny` buttons, even when voice replies are enabled.
- Incoming text prompts get a temporary eye reaction while processing.
- OpenCode can request one Telegram emoji reaction by returning a hidden `[telegram_reaction: ...]` marker, which is removed before the user sees the reply.
Expand Down Expand Up @@ -76,7 +81,9 @@ OpenCode Remote is currently a Telegram gateway for OpenCode with text, image, s

## State And Security

- The bot ignores Telegram users outside the configured allowlist.
- The bot ignores private Telegram users outside the configured user allowlist.
- The bot ignores group chats outside the configured chat allowlist. Allowed groups authorize all senders in that group, so configure only groups whose members and admins you trust.
- Group conversation memory is ephemeral, bounded, and cleared on gateway restart or OpenCode session changes. Persistent group state stores settings and known group metadata, not message text.
- Secrets are configured through private `.opencode-remote/config.json` files, not persisted settings.
- The selected active session is persisted as non-secret JSON state.
- Saved sticker packs persist only non-secret sticker identifiers and metadata.
Expand Down
33 changes: 23 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OpenCode Remote

OpenCode Remote lets you use OpenCode from Telegram. It runs on your machine, connects to your local or remote OpenCode server, and forwards messages from one authorized Telegram user to OpenCode sessions.
OpenCode Remote lets you use OpenCode from Telegram. It runs on your machine, connects to your local or remote OpenCode server, and forwards messages from authorized private users or allowed Telegram groups to OpenCode sessions.

This is a Telegram MVP with text prompts, photo prompts, sticker prompts/replies, OpenCode permission approvals, and opt-in voice input/replies. Model switching and multi-messenger support are not implemented yet.

Expand All @@ -11,7 +11,7 @@ See [Features](https://github.com/crankshift/opencode-remote/blob/main/FEATURES.
- Node.js 22.18.0 or newer. Node.js 24 LTS is recommended.
- OpenCode CLI available on the machine running the gateway.
- A Telegram bot token from BotFather.
- Your Telegram numeric user ID for the allowlist.
- One or more Telegram numeric user IDs for private-chat access, or one or more Telegram group chat IDs for group access.
- Optional voice mode: a free Groq API key for Whisper transcription and `ffmpeg` installed locally for Telegram voice-note conversion.

## Install
Expand Down Expand Up @@ -46,7 +46,9 @@ Create the config interactively:
opencode-remote setup
```

The setup flow asks whether to write a project-local or global config, then prompts for the Telegram token, allowed Telegram user ID, progress verbosity, log level, optional voice mode, and optional user-level login startup from the current project folder. If a config already exists at the chosen location, setup shows current values and pressing Enter with no input keeps them; secret values are shown only as set. If voice mode is enabled and `ffmpeg` is missing, setup can try a detected installer and then waits while you install `ffmpeg` in another terminal before continuing. Choice prompts show all options in a highlighted list with arrow-key selection and Enter to confirm.
The setup flow asks whether to write a project-local or global config, then prompts for the Telegram token, optional comma-separated user IDs allowed to DM the bot directly, optional comma-separated allowed group chat IDs, progress verbosity, log level, optional voice mode, and optional user-level login startup from the current project folder. At least one direct user ID or group chat ID is required. If a config already exists at the chosen location, setup shows current values and pressing Enter with no input keeps them; secret values are shown only as set. If voice mode is enabled and `ffmpeg` is missing, setup can try a detected installer and then waits while you install `ffmpeg` in another terminal before continuing. Choice prompts show all options in a highlighted list with arrow-key selection and Enter to confirm.

Allowed chat IDs let the gateway observe messages in those groups and decide whether they are addressed to the bot. To receive all group messages, make this bot a group admin or disable Group Privacy Mode in BotFather. To receive messages from other bots in groups, also enable Bot-to-Bot Communication Mode. Direct private messages are accepted only from configured `allowedUserIds`.

Config discovery order:

Expand Down Expand Up @@ -111,9 +113,11 @@ The config file is JSON:

```json
{
"schemaVersion": 2,
"telegram": {
"botToken": "123456:telegram-bot-token",
"allowedUserId": 123456789
"allowedUserIds": [123456789],
"allowedChatIds": [-1001234567890]
},
"voice": {
"enabled": false,
Expand All @@ -129,11 +133,15 @@ The config file is JSON:

`telegram.botToken` is required. It is the token for the bot that receives Telegram messages.

`telegram.allowedUserId` is required. Updates from other Telegram users are ignored.
`telegram.allowedUserIds` is optional when `telegram.allowedChatIds` is configured. It is an array of trusted human Telegram user IDs that may use the bot in private direct chats. Setup accepts values such as `123456789` or `123456789, 222333444`. Direct messages from other users and all private bot-to-bot messages are ignored.

`telegram.allowedChatIds` is optional when `telegram.allowedUserIds` is configured. It allows the gateway to observe every sender in those group chats, including humans and other bots, and then apply group routing settings before prompting OpenCode. Telegram group and supergroup IDs are usually negative, for example `-1001234567890`. Do not configure group IDs for groups whose members or admins you do not trust.

`opencode.apiUrl` controls the OpenCode server URL. It defaults to `http://localhost:4096`. When `opencode.autoStart=true` and this URL points to `localhost` or `127.0.0.1` with a port, the gateway starts `opencode serve --port <port>` so it waits on the same URL it configured.

`progressVerbosity` controls the startup default for the prompt activity message. Supported values are `off`, `new`, `all`, and `verbose`. The default is `verbose`. The Telegram `/progress` command can change this at runtime.
`progressVerbosity` controls the startup default for the prompt activity message in private chats. Supported values are `off`, `new`, `all`, and `verbose`. The default is `verbose`. The Telegram `/progress` command can change this at runtime in private chats. Group chats always suppress the `Activity` message.

Group behavior is managed from a private DM with the bot using `/group`. The DM menu lists known allowed groups, including groups from `telegram.allowedChatIds` and groups the bot has seen. Only configured `allowedUserIds` can use this menu. Running `/group` inside a group replies with a short notice to configure the bot in DM instead. Custom trigger phrases are configured per group from this DM menu; they are plain text, case-insensitive, and match as bounded words or phrases anywhere in text, captions, and voice transcripts.

`voice` controls optional Telegram voice input and spoken replies. `mode="on"` sends voice-note replies only after voice prompts, `mode="all"` sends voice-note replies after text, photo, and voice prompts, and `mode="off"` disables voice. When a voice-note reply succeeds, the bot does not also send the text reply; if speech generation or sending fails, it falls back to text. Voice mode requires `voice.groqApiKey` and local `ffmpeg` when enabled.

Expand Down Expand Up @@ -167,12 +175,15 @@ The bot currently supports:
/progress Show or set tool progress visibility: off, new, all, verbose
/voice Show or set voice mode
/stickers Manage saved sticker packs
/group Manage Telegram group behavior in DM
/help Show available commands
```

Any non-command text message from the authorized Telegram user is sent to OpenCode as a prompt. If no active session is selected, the gateway creates one automatically.
Any non-command text message from an authorized private Telegram user is sent to OpenCode as a prompt. In allowed group chats, messages are sent to OpenCode only when group routing settings identify them as addressed to the bot. Defaults are conservative: human senders can trigger replies by replying to the bot, mentioning the bot username, or starting text with the bot name. Per-group custom trigger phrases can also route text, captions, and voice transcripts when the phrase appears anywhere in the message. Other bots are remembered as passive context by default but do not trigger replies unless group settings are changed in the DM `/group` menu. If no active session is selected, the gateway creates one automatically.

Forwarded Telegram text, photo, album, and voice prompts include safe author context for OpenCode when Telegram provides the original author. If Telegram hides or omits the forwarded author, the prompt falls back to the authorized Telegram user without exposing raw Telegram payloads or numeric user IDs.
Allowed group chats keep bounded in-memory recent context while the gateway process runs. When a group message is routed, the gateway sends OpenCode the addressed message plus a capped recent-context transcript. It does not persist group message text; memory is cleared on gateway restart and when the active OpenCode session changes. Passive stickers and photos are stored as lightweight metadata and are not downloaded for OpenCode unless routed. Group voice messages may be transcribed before routing when voice mode is enabled so the gateway can decide whether the transcript addresses the bot.

Telegram text, photo, album, voice, and sticker prompts include safe author context for OpenCode. Forwarded prompts prefer the original author when Telegram provides it. Messages sent by anonymous admins or on behalf of a chat/channel use the sender chat title or username when available. If Telegram hides or omits usable author data, the prompt falls back to the authorized Telegram user without exposing raw Telegram payloads or numeric IDs.

When a new OpenCode session starts, OpenCode Remote sends hidden gateway context with no assistant reply. This helps the agent understand that voice input may arrive as transcripts and that final text can be delivered as voice notes when voice mode is enabled.

Expand Down Expand Up @@ -208,9 +219,11 @@ Voice commands:

## Troubleshooting

If startup fails with a configuration error, check the selected `.opencode-remote/config.json` and make sure `telegram.botToken` is non-empty and `telegram.allowedUserId` is numeric.
If startup fails with a configuration error, check the selected `.opencode-remote/config.json` and make sure `telegram.botToken` is non-empty and at least one of `telegram.allowedUserIds` or `telegram.allowedChatIds` contains a numeric ID.

If Telegram private messages from a human user appear to be ignored, confirm that `telegram.allowedUserIds` contains your Telegram user ID, not the bot ID or chat ID.

If Telegram messages appear to be ignored, confirm that `telegram.allowedUserId` matches your Telegram user ID, not the bot ID or chat ID.
If group messages appear to be ignored, confirm that `telegram.allowedChatIds` contains the group chat ID and that the message addresses the bot under the current `/group` settings. To receive all messages in groups, this bot must be a group admin or Group Privacy Mode must be disabled in BotFather. To receive messages from other bots in groups, also enable Bot-to-Bot Communication Mode.

If startup fails because OpenCode is unreachable, make sure the OpenCode CLI is installed and available in `PATH`. With auto-start enabled, the gateway waits about 60 seconds for the configured OpenCode URL before exiting.

Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
2. Global `~/.opencode-remote/config.json`.
- If no config exists, prompt the CLI user to create one.
- Ask whether the config should be local or global before writing it.
- Prompt for required values: Telegram bot token and Telegram allowed user ID.
- Prompt for required values: Telegram bot token and at least one allowed direct user ID or group chat ID.
- Prompt for progress verbosity and log level; keep OpenCode API URL, command, auto-start, and workdir on validated defaults unless users edit JSON.
- Store gateway state in the platform app-data SQLite database, separate from secrets.
- Validate `config.json` with zod and show safe, user-friendly errors.
Expand Down
Loading