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
16 changes: 10 additions & 6 deletions PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,27 +55,31 @@ No public inbound ports are required for normal usage.
- Send text prompts to OpenCode
- Accept voice/audio messages, transcribe via Whisper-compatible STT API, and forward recognized text as prompts
- Interrupt current task (ESC equivalent)
- Optionally accept text, transcribed voice, photos, rich formatted messages with photos, supported documents, and media groups sent while a task is running, at most `MAX_QUEUED_PROMPTS` (5) waiting at a time: on OpenCode V2 they wait in the session inbox and are steered into the running turn (Steer, the V2 default) or start their own run after it (Queue); on V1 the bot holds them, with at most 20 MiB of raw Telegram media bytes checked from reliable `file_size` before downloads; the V1 On/Off choice and the V2 mode are kept separately, so switching versions changes neither
- Optionally accept text, transcribed voice, photos, rich formatted messages with photos, supported documents, and media groups sent while a task is running, at most `MAX_QUEUED_PROMPTS` (5) waiting at a time: on OpenCode V2 they wait in the session inbox and are steered into the running turn (Steer, the V2 default) or start their own run after it (Queue); on V1 the bot holds them, with at most 20 MiB of raw Telegram media bytes checked from reliable `file_size` before downloads; the V1 On/Off choice and the V2 mode are kept separately, so switching versions changes neither; `/detach` leaves waiting messages to the detached session, where they are sent as if the bot had stayed attached, with the agent and model selected at `/detach`
- Handle OpenCode questions with inline options and custom text answers; the custom answer button is offered only when the question accepts a custom answer
- Questions asked by a subagent of the followed session appear in the chat like the main agent's and are answered to that subagent
- A question answered or cancelled outside Telegram (OpenCode TUI, web, another client) closes the poll on screen: its buttons go and a line says it was answered or cancelled outside Telegram
- The poll's Cancel button dismisses the whole question request in OpenCode (`question.reject`), for the main agent and for a subagent alike: once OpenCode takes it the poll turns into `❌ Poll cancelled`, answers already chosen are not sent, and the agent's turn ends without a reply or footer; a Cancel that does not reach OpenCode leaves the poll answerable with a line saying so
- In a multi-select question the custom text becomes one more tickable row next to the options, and Done sends it together with the ticked options
- Send selected/custom answers back to OpenCode (`question.reply`); on V2 a tapped choice is sent as the value OpenCode expects, while the buttons and the summary show its label
- Handle permission requests interactively (`allow once` / `always` / `reject`), from the main agent and from subagents of the followed session
- A permission prompt stays in the chat when it ends: its buttons go and a last line names the outcome — the decision tapped here, the decision made outside Telegram (or just that it was answered there, when OpenCode does not say how), or "not answered" when it was dropped by `/abort`, `/detach`, `/opencode_stop` or the end of the run
- A permission prompt stays in the chat when it ends: its buttons go and a last line names the outcome — the decision tapped here, the decision made outside Telegram (or just that it was answered there, when OpenCode does not say how), or "not answered" when it was dropped by `/abort`, `/detach`, `/opencode_stop`, a restart of the OpenCode V2 server or the end of the run
- An answer that does not reach OpenCode leaves the prompt answerable with a warning line; tapping again sends it again
- After the event stream reconnects, prompts on screen that OpenCode no longer has pending are closed as answered outside Telegram
- After the event stream reconnects to the same server, prompts and polls on screen that OpenCode no longer has pending are closed as answered (or cancelled) outside Telegram; when the bot cannot tell whether the server restarted, the same line is used
- On OpenCode V2, a poll on screen when the server goes away — `/opencode_stop`, a restart outside the bot, a crash — keeps its question text, loses its buttons and ends with `⏹ Not answered`
- Starting the bot, attaching, `/sessions` and an event-stream reconnect show one prompt per distinct pending permission request (identical ones grouped) and one poll per pending question, however many of them run at once

### Result delivery

- Send each completed assistant response after completion signal from SSE
- When the assistant footer is on, every answered turn of the followed session ends with its own footer — including a turn OpenCode starts by itself after a background command or subagent ends (V2) and a prompt typed in an attached OpenCode TUI or Desktop — with that turn's agent and model and the time from its own start; a turn that is aborted, errors, or is stopped from an attached client gets none
- OpenCode V2 resumes a run interrupted by a server restart (`/opencode_stop` then `/opencode_start`, a restart outside the bot, a crash and auto-restart); the chat ends the interrupted turn as after `/abort` — its tool lines, cards and compact progress end, no footer — and shows the resumed run as a turn OpenCode started by itself, with its own prompts, reply and footer, timed from when the bot saw it again
- In draft streaming mode, assistant text written before a question or permission prompt is sent as a message above that prompt when it appears, and is not sent again when the reply completes
- If that send fails, the reply is not sent again; when Telegram accepts sends again, the chat gets a notice that the last assistant reply was not delivered
- In edit streaming mode the chat reads in the order things happened: a reply the agent wrote before its next tool call, thinking, subagent, document, question or permission prompt sits above it, and the next tool opens a new message (in compact mode, a new progress message) below the reply
- After a mid-session Telegram outage, the next new message is answered without restarting the app
- Compact output mode shows thinking and writing on one progress message per stretch of work between replies and prompts, from the start of that stretch; the message is removed or marked finished when the reply or prompt lands, or when the run ends if nothing followed. A message whose stretch started a background operation (OpenCode V2) stays working on that operation with its timer past the reply, prompt or end of the run, and is removed or marked finished with its own counts when its last background operation ends
- In full mode, show every foreground tool operation when it starts, without a timer until 20 seconds; edit its line in place as it runs and finishes, keeping parallel operations in start order. A finished call lasting at least 20 seconds shows its total duration. Subagent (`task`) operations use their cards instead of tool lines; a tool delivered as a document loses its running text line when the document arrives. A background command or subagent (OpenCode V2) keeps its running line or card, with its timer, after the turn ends and gets its finished line or `✅ Completed` with the total duration when the operation itself ends; after `/abort`, a session switch or a lost event stream it stays as it was
- In full mode, show every foreground tool operation when it starts, without a timer until 20 seconds; edit its line in place as it runs and finishes, keeping parallel operations in start order. A finished call lasting at least 20 seconds shows its total duration. Subagent (`task`) operations use their cards instead of tool lines; a tool delivered as a document loses its running text line when the document arrives. A file-changing tool (`edit`, `write`, `apply_patch`) names each file it changed on its own line with `(+N -M)`, never the patch text, on both OpenCode versions; with diff-file attachments on, each file arrives as its own document captioned with its line, and a file whose diff is over the size limit keeps its text line. A background command or subagent (OpenCode V2) keeps its running line or card, with its timer, after the turn ends and gets its finished line or `✅ Completed` with the total duration when the operation itself ends; after `/abort`, a session switch or a lost event stream it stays as it was. A foreground line whose call ended while the event stream was down also stays as it was; the bot never shows a tool it did not see start
- Show elapsed time for tool calls running longer than 20 seconds, updated on a timer so it keeps counting while a tool blocks without producing output; covers subagent cards and compact mode, and the total duration stays on the finished tool line. In compact mode, while several tools of one step are in flight, the progress line shows the still-running one (the most recently started if several), with that tool's timer — not a finished sibling. A finished subagent card keeps the time its whole run took. Durations use the same `· 🕒 1h 2m 3s` format as the assistant run footer
- A subagent card shows Task, Agent, and Model; when OpenCode sends a variant, the Model line is `provider/id (variant)`
- Render assistant replies with native Telegram formatting: real tables with the column alignment declared in markdown, bullet lists with their nesting, block quotes that keep their nested content, headings, and syntax-highlighted code. Numbered lists and checklists keep literal markers (`1.`, ✅/🔲), because Telegram clients number a native ordered list from zero and do not draw the native checkbox at all
Expand Down Expand Up @@ -128,7 +132,7 @@ Current command set:
- `/status` - bot version, server, project, and session status
- `/new` - create a new session
- `/abort` - stop the current task
- `/detach` - detach the bot from the current session without stopping it; a later command or prompt HTTP failure for that session is not posted to chat unless the bot has re-attached to it
- `/detach` - detach the bot from the current session without stopping it; messages waiting for its running task stay with it and reach it as if the bot had stayed attached (no buttons, withdrawn only by `/abort` there or `/opencode_stop`); a later command or prompt HTTP failure for that session is not posted to chat unless the bot has re-attached to it
- `/sessions` - show and switch recent sessions
- `/recent` - show recent sessions across projects and worktrees with their status and switch directly to one
- `/messages` - browse user messages in the current session
Expand Down Expand Up @@ -215,7 +219,7 @@ Agent picker behavior:
- [x] Attaching a project file from `/ls` to the next prompt as a native OpenCode file part
- [x] `/messages` command: browse session messages with revert and fork functionality
- [x] Optional message queue for text, voice, photos, rich formatted messages with photos, documents, and media groups sent while the agent is busy, managed from the bottom keyboard
- [x] OpenCode V2: messages sent mid-run are steered into the running turn or queued in the session inbox (Off / Queue / Steer in `/settings`), withdrawable until picked up
- [x] OpenCode V2: messages sent mid-run are steered into the running turn or queued in the session inbox (Off / Queue / Steer in `/settings`), withdrawable until picked up or until `/detach` leaves them to the session
- [x] Native Telegram rich message formatting for assistant replies (Bot API 10.1)
- [x] Incoming Telegram rich formatted messages (Bot API 10.1): converted to Markdown, accepted anywhere text is accepted, with photos attached and unsupported message types answered explicitly
- [x] Startup either reaches Telegram polling or the process exits: transient Telegram failures are retried in-process; a bad token or other fatal startup error exits with code 1
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Languages: English (`en`), العربية (`ar`), Deutsch (`de`), Español (`es`
- **Voice prompts** — send voice/audio messages, transcribe them via a Whisper-compatible API, and optionally enable spoken replies in `/settings`
- **File attachments** — send images, PDF documents, and text-based files to OpenCode, including multiple files in one Telegram album
- **Scheduled tasks** — schedule prompts to run later or on a recurring interval; see [Scheduled Tasks](#scheduled-tasks)
- **Message queue** — messages sent while the agent is busy are held and sent one by one afterwards (Queue) or, on OpenCode V2, steered into the running task (Steer); each waiting message is a bottom-keyboard button you can tap to withdraw it
- **Message queue** — messages sent while the agent is busy are held and sent one by one afterwards (Queue) or, on OpenCode V2, steered into the running task (Steer); each waiting message is a bottom-keyboard button you can tap to withdraw it; `/detach` leaves waiting messages to the session they were sent to
- **Context control** — tap the bottom 📊 button to see context usage and the latest assistant message's tokens and cost; compact from the details with an inline confirmation
- **Input flow control** — when an interactive flow is active, the bot accepts only relevant input to keep context consistent and avoid accidental actions
- **Git worktree switching** — browse and switch between existing git worktrees for the current repository with `/worktree`
Expand Down Expand Up @@ -309,6 +309,8 @@ Runtime preferences are changed from `/settings` and stored in `settings.json`:

With the message queue on, text, transcribed voice, photos, rich formatted messages with photos, supported documents, and media groups sent while the agent is busy are accepted instead of being turned down. At most `MAX_QUEUED_PROMPTS` (5) messages wait at a time. Waiting messages appear as buttons above the usual bottom-keyboard grid — tap one to withdraw it — and `/abort`, `/opencode_stop` or a session/project switch withdraws them all. When a waiting message is picked up, its button disappears and its text is quoted as external user input.

`/detach` does not withdraw them: they stay with the detached session and reach it as if the bot had stayed attached, with the agent and model selected at `/detach` — a message still being transcribed or downloaded included. Their buttons leave the keyboard and they no longer count toward the limit. A later session or project switch leaves them alone; `/abort` after returning to that session, or `/opencode_stop`, withdraws them. Picked up while detached, they show nothing in the chat beyond the usual background notification; back in the session before pickup, each is quoted as external user input when it starts.

On OpenCode V2 a waiting message is sent to OpenCode at once and waits in the session's inbox, not in the bot: with `Steer` the running task picks it up at its next step and keeps going in the same progress message with one footer at the end; with `Queue` it starts its own run once the task finishes. Nothing is held by the bot, so there is no queued-media size limit, and after a bot restart the buttons are gone while OpenCode still delivers the messages.

On OpenCode V1 the bot holds the messages itself and sends them one at a time as each run finishes. Its queue also holds at most 20 MiB of raw Telegram media bytes in total; the limit is checked from reliable Telegram `file_size` metadata before media is downloaded or prepared, while base64 data-URI expansion is not counted. Queued media without a reliable source size is refused while the task is busy.
Expand Down
11 changes: 11 additions & 0 deletions docs/release-notes/v0.26.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Fixes
- **Subagent questions reach the chat** — a question asked by a subagent now appears as an ordinary poll you can answer in Telegram, instead of never showing up while the run silently waits for it.
- **Permission prompts and polls keep their outcome** — a finished permission prompt is no longer deleted and a poll settled elsewhere no longer keeps live buttons: both stay in the chat with a closing line such as `✅ Allowed once`, `☑️ Answered outside Telegram` or `⏹ Not answered`.
- **Undelivered permission replies can be retried** — when a permission reply does not reach OpenCode, the prompt now keeps its buttons with a "tap again" warning instead of disappearing behind an error while the request stays stuck.
- **Poll Cancel reaches the agent** — tapping `❌ Cancel` on an agent's poll now dismisses the question in OpenCode, so the turn ends and queued messages run instead of the agent waiting for an answer indefinitely.
- **`apply_patch` shows a summary, not the patch** — file changes made with `apply_patch`, as GPT models do, now show one `🩹 apply_patch <file> (+N -M)` line and diff document for every changed file, instead of the whole patch on OpenCode V2 or only the first file on V1.
- **`edit` on OpenCode V2** — file edits now show their `(+N -M)` counts and diff document as on V1, and the compact card's `changed files` counts files changed by `edit` and `apply_patch` instead of showing 0.
- **OpenCode V2 restart during a run** — the interrupted turn now ends as after `/abort` and the run OpenCode resumes on its own shows as a separate turn, with no stray `🛠️ unknown` line, background-session notice or duplicate permission prompts.
- **Messages kept on `/detach`** — a message sent to a running session right before `/detach` is no longer lost and reaches that session once its turn picks it up or ends, on both V1 and V2.

Full changelog: https://github.com/grinev/opencode-telegram-bot/compare/v0.26.1...v0.26.2
22 changes: 11 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@grinev/opencode-telegram-bot",
"version": "0.26.1",
"version": "0.26.2",
"description": "Telegram bot client for OpenCode to run and monitor coding tasks from chat.",
"type": "module",
"main": "./dist/index.js",
Expand Down
Loading
Loading