docs(telegram): message context, grouping and presence

InputDebounce config row, the Using it section (replies, quotes, forwards, local-time header, grouping window, eyes reaction, online message, forwards never count as the login code), the Inbound paragraph in CLAUDE.md, and spec status set to implemented.
This commit is contained in:
EugeneTes
2026-09-26 20:09:20 +02:00
parent 98faaf3a12
commit 0c1778e741
3 changed files with 25 additions and 6 deletions

View File

@@ -94,7 +94,10 @@ and `ClaudeCode` + `ClaudeCode.Bridge`. The Claude Code provider is the most int
Long polling (`Polling/`, with `ReconnectPolicy` backoff). Each private chat gets its own `AgentSession`
(`Chats/ChatRegistry`), with workspace `<WorkspaceRoot>/<chatId>` and session scope `telegram-<chatId>`. Access is
limited by `AllowedUserIds`. The first allowed id is the "owner": `OwnerLoginChannel` sends Claude Code re-login
limited by `AllowedUserIds`. Incoming messages go through `Inbound/`: the router takes a `ChatInbox` lease per
message in update order, `InboundComposer` builds its part (reply/forward headers from `MessageContext`, text and
photo) in the background, and the inbox flushes a batch `InputDebounce` (600 ms) after the last message as one prompt
with a local-time header. The first allowed id is the "owner": `OwnerLoginChannel` sends Claude Code re-login
prompts to that chat. It resolves lazily to break the DI cycle ChatRegistry → session factory → extension → ChatRegistry.
Answers go out complete, not streamed (`Rendering/`). Tests drive the host through `Support/FakeBotClient` and `ChatHost`.

View File

@@ -1,7 +1,7 @@
# Telegram message context, grouping and presence — design
Date: 2026-09-26
Status: draft
Status: implemented
Depends on: `2026-09-26-telegram-attachments-design.md`, which plugs its content steps (files, speech) into
`InboundComposer` and uses the renderer's `RunInOrderAsync` for notices and the `🎤` echo.
Also `2026-09-26-scheduler-design.md` §7 (`ChatSettingsStore`, the chat's time zone for the timestamp header; if this spec

View File

@@ -31,6 +31,7 @@ Environment variables, or the `Telegram` section of `appsettings.json` (`Telegra
| `Telegram:WorkspaceRoot` | `~/.volcanic/telegram-workspaces` | Chat `<id>` works in `<root>/<id>`; put `AGENTS.md`, `PERSONA.md`, `MEMORY.md` there |
| `Telegram:Model` | `claude-code/claude-opus-5-5` | Any `provider/model` the Claude Code provider serves |
| `Telegram:EditInterval` | `00:00:01.5` | Minimum time between edits of the "Working..." status message |
| `Telegram:InputDebounce` | `00:00:00.6` | How long the bot waits after a message for more before sending them to the agent as one request. `0` sends each message on its own. Must not be negative |
| `Telegram:ReconnectInitialDelay` / `ReconnectMaxDelay` | `1 s` / `60 s` | Backoff after lost connection |
| `PROCESS_ENV_ALLOW` | — | Comma-separated names or globs let through to `bash`, scheduled scripts and MCP servers despite the deny-list (e.g. `GH_TOKEN` for `gh`). Added to `ProcessEnvironment:Allow` |
| `ProcessEnvironment:Deny` / `ProcessEnvironment:Allow` | built-in deny-list | Added to the defaults (`*_API_KEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, their `Section__Key` forms and the named provider credentials), never replacing them. Allowing a name that is denied by name stops startup |
@@ -53,7 +54,22 @@ Write normally. While the agent works, a status message stays at the bottom of t
It lists the last five tool calls (`▶` running, `✓` done, `✗` failed) and is deleted when the run finishes.
Answers are not streamed: each one is sent complete when the model finishes it (split into several messages past
Telegram's 4096 character limit).
A message sent while the agent works steers the current run. Photos (with or without a caption) are passed to the model.
A message sent while the agent works steers the current run, and the bot reacts to it with 👀 so you can see it was
added. Photos (with or without a caption) are passed to the model.
Along with your text, the agent sees:
- **Replies and quotes.** Reply to one of the bot's messages (or one of yours), or quote part of it, and the agent gets
a line such as `[Reply to your message: "Which plant do you mean?"]` before your text.
- **Forwards**, labelled with their source: `[Forwarded from Daily News (Anna K.)]`.
- **The local time** you sent the message, in the chat's time zone (for now, the zone of the machine running the bot):
`[Sat 2026-09-26 14:03 Europe/Berlin]`, once per request.
Messages sent within about 0.6 s of each other (`Telegram:InputDebounce`), such as a forward with your comment or
an album, reach the agent as one request. Every message waits that long before it's sent.
After a start the bot sends `🟢 Online.` to the owner (the first id in `AllowedUserIds`), once. If the owner never
started a chat with the bot, Telegram refuses the message; the bot logs that and carries on.
| Command | |
|---|---|
@@ -74,9 +90,9 @@ gets the re-login link and code prompt in their own private chat, regardless of
it. That user must already have started a chat with the bot — Telegram bots cannot message a user first — or
the login goes unanswered and the triggering request fails ("no code was entered"). The link, the steps and the
code prompt arrive as one message. While that prompt is pending, the owner's next plain text message in that chat
is taken as the code and is not sent to the agent; photos and `/commands` still behave normally. Don't ask the bot
something else in the owner chat until the code is in: a second free-text question in that chat (for example from
an extension) replaces the pending prompt and cancels the login. See
is taken as the code and is not sent to the agent; photos and `/commands` still behave normally. A forwarded message
never counts as the code. Don't ask the bot something else in the owner chat until the code is in: a second free-text
question in that chat (for example from an extension) replaces the pending prompt and cancels the login. See
[claude-code-provider.md](claude-code-provider.md#session-expiry-and-re-login).
## Security