@llblab/pi-telegram 0.25.6 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/BACKLOG.md +16 -1
- package/CHANGELOG.md +11 -0
- package/README.md +5 -5
- package/docs/activity.md +2 -2
- package/docs/architecture.md +2 -2
- package/docs/outbound.md +5 -5
- package/docs/public-api.md +2 -2
- package/docs/ui-style.md +1 -1
- package/index.ts +2 -0
- package/lib/activity-verbosity.ts +141 -15
- package/lib/config.ts +17 -4
- package/lib/menu-settings.ts +5 -5
- package/lib/telegram-api.ts +1 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -89,7 +89,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
89
89
|
- Instance thread names are provisioner-authored identity metadata, not model-authored output. Fresh Threaded Mode threads select one of five curated compact 4-6 letter Latin names for the assigned slot letter using provisioning timestamp entropy, then create the Telegram thread with that thread name immediately. The title is the thread name itself, not `{slot} {threadName}` or `{slot} — {threadName}`. Do not register or reintroduce an agent-facing thread rename tool; removing the extra rename prompt/tool turn is an intentional simplicity constraint. Thread names must mirror into terminal status, live diagnostics, and `[telegram|thread:name]` through one target-aware current-instance identity resolver. Registered follower/leader metadata takes precedence over a stale shared record for the same target, with the record used only as fallback; do not hide a valid baked/custom thread name behind old slot-prefix validation. A follower that later becomes leader keeps its existing name; leadership changes are transport role changes, not identity resets.
|
|
90
90
|
- Telegram private-chat Threaded Mode treats `All` as an aggregate/control surface, not a durable `General` thread or process launcher, and leader activation proactively creates or reuses its private-DM thread. When the owner writes in an unknown thread, the leader reclaims and routes the first one only if it lacks an active binding; later unknown threads remain intact and receive an in-thread chooser to reroute the captured prompt or restore a stale binding. Destructive cleanup requires explicit confirmation through `thread-reconciler`; another Pi instance still requires a manually started process followed by `/telegram-connect`. Without Threaded Mode, process the message through classic routing.
|
|
91
91
|
- Telegram extension work should not hold Pi's core agent lifecycle hostage once Pi has produced the semantic result. For Telegram-originated turns, final Telegram delivery, attachment upload, and transport cleanup are extension-owned side effects; schedule them off the critical `agent_end` path when ordering can still be preserved, record failures as runtime events, and keep dispatch of the next Telegram turn behind the delivery task when needed to avoid interleaving previews/finals. Treat a low-level error `agent_end` as retryable while its Telegram turn remains active; only a later successful `agent_end` or Pi's `agent_settled` boundary may finalize and release that turn. Public activity handlers run through isolated asynchronous per-handler queues; lifecycle hooks enqueue normalized events and never await consumer work. Proactive push defaults enabled and requires explicit `assistant.proactivePush: false` to opt out. While enabled, core delivery projects each completed Pi-visible assistant text block from local/autonomous work once and in order; bind admitted blocks to the exact target, profile/token transport stamp, direct leader epoch or follower registration generation, and session generation. Token deltas, hidden reasoning, tool traffic, stale authority, and Telegram-owned turns never enter that projection.
|
|
92
|
-
- Core thinking/tool visibility is controlled by `assistant.activity: "quiet" | "thinking" | "tools" | "verbose"
|
|
92
|
+
- Core thinking/tool visibility is controlled by file-backed `assistant.activity: "quiet" | "thinking" | "tools" | "verbose"`; every Pi instance reloads the shared config at `agent-start` before admitting activity so process-local cache cannot broaden the selected class. It defaults to `verbose` when absent and remains separate from proactive public prose and assistant Markdown: provider-exposed thinking uses durable ordinary HTML with a standard expandable blockquote, while completed executed tools use native Rich Messages with separate closed details and JSON pre blocks for arguments, retained updates, and result/error evidence. Thinking uses the sentence-case `🧠 Thinking: <level>` header with the current Pi thinking level instead of a generic lifecycle status and renders inline Markdown as Telegram HTML inside its expandable blockquote; tools use sentence-case `🛠 <Tool>: <status>` headers, `verbose` enables both, and every activity send/edit disables Telegram link previews while technical evidence also neutralizes HTTP(S) auto-link detection as a client-safe fallback. Keep it quiet by default, exact-target/transport-generation fenced, start-order preserving, boundary-aware, memory/transport bounded, and non-blocking; final delivery waits for admitted technical activity only inside the extension-owned background task, and session replacement abandons queued old-generation work without waiting on old transport. Read legacy `assistant.activityVerbosity` only when `assistant.activity` is absent and remove it on the next Settings write. Never invent unavailable provider-private thinking or replay a possibly committed technical message.
|
|
93
93
|
- Telegram runtime state should be treated as event-driven reconciliation of extension assumptions against observable Telegram signals, not as a full Telegram bot read-model and not as a reason to query Telegram on every action. Keep a local assumption model for bot identity/capabilities, pairing assumptions, thread support, known thread lifecycle, bound instance targets, reservations, and transport health. Invalidate and reconcile the relevant slice on meaningful events: startup/reload, lifecycle service messages, stale-send/API errors, setup/pairing changes, follower register/prune, explicit status/debug requests, and bounded low-frequency leader health ticks. `lib/sync.ts` owns sync slices, observation intake, invalidation triggers, status/debug freshness, and reconciliation scheduling; it must not promise complete bot-state mirroring because Bot API lacks a complete topic listing surface. `lib/thread-reconciler.ts` owns pure Threaded Mode lifecycle planning and should become the only policy authority for destructive thread cleanup decisions. `threads` owns current Telegram UI thread/tab binding primitives and thread-name helpers mapped to Bot API topic transport, `status` renders projections, and `index.ts` only wires ports. `tmp/telegram/logs.jsonl` is a session-local redacted runtime evidence stream for race debugging and resets on extension start / runtime scope changes; it is diagnostics only, not routing/provisioning authority. `state.json` should be an observable/debug snapshot aligned with `/telegram-status` (extension runtime, bot capabilities including `bot.lastSlot`, runtime role, live roster projection, reservations, diagnostics), not routing/provisioning authority. Because every process on one Telegram profile shares this file, only the active transport lock owner may persist it; followers read current state and gain write authority only after promotion. Status-only writes must refresh disk-backed bindings before serialization so a stale loaded snapshot cannot erase newer leader state. Live bus/runtime state is authoritative; file reservations and fresh capability observations may be startup hints/collision guards only; stale snapshots must re-probe before suppressing bus/topic behavior. Do not persist stale/offline/failed thread target history as source of truth.
|
|
94
94
|
- Thread bindings are bidirectional. From the Pi-instance side, an instance knows and preserves its target, slot, thread name, lifecycle state, and direct-delivery defaults. From the Telegram-client/bot side, the bridge observes thread creation/close/reopen/stale-send signals and reconciles them into instance binding state. Lifecycle transitions that affect operator understanding should be Telegram-visible when a live transport remains: every successful follower registration/re-registration gets a compact connected notice in the assigned thread, while heartbeat pruning stays silent because it is only liveness suspicion. These notices should use the instance thread name when known and fall back to the slot letter only while the thread is still unnamed. New unknown or command-created temporary threads receive the complete forward plus replace/restore control set and remain preserved until the user chooses one. Successful forward deletes the chooser and temporary thread; successful replace/restore always deletes the chooser, rebinds the temporary thread, and deletes only the replaced old thread. Any destructive cleanup must go through `thread-reconciler`. Unknown `forum_topic_created` service events are observations, not destructive cleanup proof.
|
|
95
95
|
- The multi-instance bus uses private bot DMs with Telegram private-chat Threaded Mode enabled for the bot. No supergroup, group, or forum configuration is needed; the thread chat is always the private chat with the paired owner. Threaded capability checks must use bot/private-chat evidence such as `getMe.has_topics_enabled`, incoming `message_thread_id`, and topic operation success/failure; do not use group chat metadata as the control-plane truth for Telegram private-chat Threaded Mode.
|
|
@@ -121,7 +121,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
121
121
|
- Forward-annotation grouping distinguishes two semantic layers: the forward owns its source text/caption/media, while an optional separate owner-authored annotation normally arrives immediately before it. Use a bounded one-second candidate window in both transport orders: ordinary short human text may wait for an adjacent same-sender/chat/thread forward, and a forwarded message—including a media-only forward with no source caption—may wait for one adjacent owner annotation. A matching opposite-kind message flushes immediately; same-kind messages, commands, bots, ordinary non-forward captions, media groups, target/sender mismatches, reversed ids, and distant ids stay separate. Preserve the candidate across session replacement and follower forwarding without weakening exact routing-generation authority.
|
|
122
122
|
- `telegram_voice` and `telegram_button` are not Pi tools; keep prompts/docs explicit that agents should author markup while voice synthesis provider extensions own TTS/OGG conversion, and pi-telegram owns button routing plus Telegram delivery
|
|
123
123
|
- Voice reply policy and prompt context are owned by pi-telegram's `telegram.json` `voice.replyMode`: missing, invalid, `hidden`, and legacy `manual` config resolve to `hidden` and add no `[voice]` context; `mirror` adds `[voice] delivery: automatic voice` only for voice/audio input, while `always` adds that same single factual line to every Telegram turn. Place voice context after `[outputs]` when handler output exists, otherwise after `[attachments]`; provider prompt contributions are optional provider-specific additions, not the default policy or delivery-composition channel.
|
|
124
|
-
- Optional Telegram wall-clock context may add `[time] YYYY-MM-DD HH:mm:ss <timezone>` to Telegram-originated prompts. It
|
|
124
|
+
- Optional Telegram wall-clock context may add `[time] YYYY-MM-DD HH:mm:ss <timezone>` to Telegram-originated prompts. It defaults to `interval` without migrating explicit settings, uses `assistant.timeInjection` values `hidden|always|interval`, stores the interval duration separately as `time.interval` in milliseconds, uses the system timezone, and should render last after `[attachments]`, `[outputs]`, and `[voice]` sections. The Settings row `🕒 Time injection: hidden|always|interval` controls only `assistant.timeInjection`; the old `time.injectionMode` key is ignored and is not migrated.
|
|
125
125
|
- Agent activity status is native-only for the release path: Telegram uses `sendChatAction(typing)` and renders its native `…typing` indicator as the automatic work signal. When a connected instance starts any real agent run, send that indicator to the instance's own thread (and aggregate `All` surface), including Telegram turns, local/TUI prompts, and autonomous continuations such as Grow Loop; stop it at agent/session completion. Keep at most one chat action in flight per loop, coalesce identical leader-side actions across follower traffic, and treat Telegram 429 `retry_after` as a shared suppression window rather than a delayed retry or persistent terminal error. This signal is independent from terminal `Active`, which remains scoped to Telegram-owned turns. Confirmed manual `/compact` and automatic compaction tied to an active Telegram turn also use native activity. Do not send activity merely for startup/connect/reload/recovery or add extra in-chat work surfaces without a new explicit design decision.
|
|
126
126
|
- Voice reply mode Settings UI standard: the top-level Settings row is `👄 Voice reply: hidden|mirror|always`; `hidden` is the true default, persists no `voice.replyMode`, adds no voice context, and still allows explicit agent-authored `telegram_voice`; `mirror` activates automatic voice context only for voice/audio input, and `always` activates it for every Telegram turn. The submenu title is `👄 Voice reply mode:`; choice buttons use lowercase labels with a model-style active dot (`🟢 hidden`, `🟢 mirror`) rather than per-mode emoji; the explanatory submenu body uses compact HTML-code bullets such as `<code>-</code> <code>hidden</code> (default): ...`. Preserve this wording/icons unless the operator explicitly asks to redesign it
|
|
127
127
|
- Outbound voice delivery is one fallback pipeline: configured `outboundHandlers` with `type: "voice"` run first in `telegram.json` order, then programmatic voice handlers, then registered voice synthesis providers as zero-config progressive fallbacks; provider extensions must not override operator-configured handlers
|
package/BACKLOG.md
CHANGED
|
@@ -2,9 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
_This backlog tracks only open release-relevant work: hotfixes, bounded maintenance, live runtime verification, evidence-gated Telegram client follow-ups, and upstream Pi API blockers. Completed outcomes and validation evidence belong in `CHANGELOG.md`, not in this queue._
|
|
4
4
|
|
|
5
|
+
## P0 — Rich Tool Activity And Discoverable Defaults Release
|
|
6
|
+
|
|
7
|
+
Context: Thinking keeps its current persistent HTML expandable quote. Tool activity moves to native Rich Messages so arguments, retained updates, and result/error evidence become separate collapsed details containing compact JSON code blocks. New installations should discover context and activity features through `assistant.timeInjection: "interval"` and `assistant.activity: "verbose"` defaults without migrating explicit existing settings.
|
|
8
|
+
|
|
9
|
+
Open work:
|
|
10
|
+
|
|
11
|
+
- [x] Render completed tool batches through `sendRichMessage` / `editMessageText.rich_message` with one sentence-case tool header followed by separate closed `Arguments`, `Update N`, and `Result`/`Error` details containing `json` pre blocks.
|
|
12
|
+
- [x] Preserve tool ordering, coalescing, redaction, truncation, dropped-update evidence, transport/authority fencing, message bounds, final-answer ordering, and commit-unknown no-replay behavior; use the existing HTML representation only for a known-safe Rich capability rejection.
|
|
13
|
+
- [x] Change absent Activity mode to `verbose` and absent time injection mode to `interval` with the existing one-hour interval fallback; preserve every explicit stored value without migration.
|
|
14
|
+
- [x] Update tests, README, technical docs, durable project rules, changelog, and package metadata for the final behavior.
|
|
15
|
+
- [ ] Publish and verify GitHub/npm release evidence for `0.26.0`.
|
|
16
|
+
- [ ] Live-smoke Rich tool details on Telegram mobile and Desktop in classic and Threaded Mode, including multi-tool batches, updates, truncation, failures, follower transport, and final-answer ordering.
|
|
17
|
+
|
|
18
|
+
Done when: native Rich tool activity is compact and logically segmented, explicit settings remain authoritative, new defaults are discoverable, automated validation passes, live clients confirm the rendering/routing contract, and the release is published through the repository release flow.
|
|
19
|
+
|
|
5
20
|
## P1 — Configurable Activity Live Smoke
|
|
6
21
|
|
|
7
|
-
Context: configurable activity
|
|
22
|
+
Context: configurable activity offers quiet, thinking-only, tools-only, and verbose modes. Thinking uses persistent collapsed ordinary HTML instead of draft-only Rich Thinking blocks; completed tools use native Rich details with JSON code blocks.
|
|
8
23
|
|
|
9
24
|
Open work:
|
|
10
25
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.0: Rich Tool Activity
|
|
4
|
+
|
|
5
|
+
- `Tool Presentation`: Moved completed tool batches from one ordinary HTML expandable quote to native Rich Messages. Each sentence-case tool header now precedes separate closed `Arguments`, retained `Update N`, and `Result`/`Error` details containing compact JSON code blocks; dropped-update counts remain visible in the first retained update summary. Thinking keeps its distinct persistent HTML expandable quote. Impact: technical history becomes easier to scan, tool phases no longer compete inside one large JSON disclosure, and thinking remains optimized for prose.
|
|
6
|
+
- `Activity Safety`: Preserved tool-start ordering, batch coalescing, redaction, per-field truncation, target and generation fencing, final-answer ordering, and no replay after ambiguous sends. Rich activity disables entity detection; a known-safe Rich HTTP 400 rejection falls back once to the previous HTML representation. Impact: the richer UI retains the existing bounded privacy and transport guarantees without duplicate technical messages.
|
|
7
|
+
- `Discoverable Defaults`: Changed absent `assistant.activity` to `verbose` and absent `assistant.timeInjection` to `interval` with the existing one-hour interval fallback. Explicit stored settings remain authoritative and receive no migration. Impact: new installations discover technical activity and periodic wall-clock context immediately, while existing operator choices remain unchanged.
|
|
8
|
+
|
|
9
|
+
## 0.25.7: Multi-Instance Activity Isolation Hotfix
|
|
10
|
+
|
|
11
|
+
- `Activity Config`: Reloaded the shared file-backed `assistant.activity` setting in every Pi process at `agent-start` before activity admission, failing closed for that run if refresh fails. Impact: selecting `thinking` emits only thinking, selecting `tools` emits only tools, `verbose` emits both, and `quiet` emits neither across all live instances instead of stale process-local config behaving like an older broader mode.
|
|
12
|
+
- `Activity Labels`: Capitalized human-facing technical headers as `🧠 Thinking: <level>` and `🛠 <Tool>: <status>`. Impact: persistent activity messages follow the same sentence-case UI convention as menus and settings.
|
|
13
|
+
|
|
3
14
|
## 0.25.6: Same-Profile Thread Reuse Hotfix
|
|
4
15
|
|
|
5
16
|
- `Leader Restart`: Deferred a pending leader cleanup when its target still belongs to the replacement's active stable profile, restored that existing binding first, and then cancelled the superseded cleanup intent. Unrelated cleanup intents still execute before provisioning. Impact: an interrupted graceful shutdown no longer causes the next launch of the same Pi instance to delete and recreate its reusable Telegram thread.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
It is a **runtime adapter**, not a remote terminal. Start or supervise work in the Pi TUI, then continue from Telegram while away from the keyboard. Each Telegram destination follows a running Pi instance and sends prompts into that instance's currently active session; it is not permanently bound to one session file or session identity. The bridge preserves Pi session semantics instead of pretending Telegram is a PTY, shell, process launcher, or session browser. That boundary is the product: Telegram gets safe runtime handles, not raw terminal power.
|
|
10
10
|
|
|
11
|
-
Proactive push is enabled by default. `assistant.proactivePush` projects every completed public assistant text block from local or autonomous work—including visible checkpoints and the final answer—to the authorized Telegram target once and in order; set it explicitly to `false` to disable projection. Proactive push itself never mirrors local prompts, thinking, tool traffic, token deltas, Telegram-owned turns, or stale-generation work. The separate
|
|
11
|
+
Proactive push is enabled by default. `assistant.proactivePush` projects every completed public assistant text block from local or autonomous work—including visible checkpoints and the final answer—to the authorized Telegram target once and in order; set it explicitly to `false` to disable projection. Proactive push itself never mirrors local prompts, thinking, tool traffic, token deltas, Telegram-owned turns, or stale-generation work. The separate `Activity` setting defaults to `verbose` so new installations discover collapsed provider-exposed thinking and tool evidence immediately; operators can narrow it to one class or choose `quiet`. See [Outbound](docs/outbound.md#proactive-public-output) and the [configuration reference](docs/public-api.md#configuration-api).
|
|
12
12
|
|
|
13
13
|
This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`cb34008`](https://github.com/badlogic/pi-telegram/commit/cb34008460b6c1ca036d92322f69d87f626be0fc) and has since diverged substantially.
|
|
14
14
|
|
|
@@ -101,9 +101,9 @@ The first Telegram user to message the bot becomes the allowed owner. Other user
|
|
|
101
101
|
| Model and thinking | Switch model or thinking level from Telegram through safe continuation flows. | Mobile control can adjust execution strategy without tearing down the current session. |
|
|
102
102
|
| Compaction | Confirm `/compact`, show native active status during compaction, and preserve Telegram-owned turn semantics. | Context maintenance is visible and safe from the phone. |
|
|
103
103
|
| Draft previews | Show Telegram's native `…typing` indicator whenever the connected instance is doing agent work, or enable Rich Draft previews for streamed answer text. | Local prompts, Telegram turns, and autonomous continuations remain visibly active while draft visibility stays independent from final rendering. |
|
|
104
|
-
| Activity | Keep the default `
|
|
104
|
+
| Activity | Keep the default `verbose` technical surface, show only `thinking`, show only `tools`, or select `quiet` for answer-only delivery. Every instance reloads this shared file-backed choice before a new agent run, and thinking headers report the current Pi thinking level instead of a generic completion state. | Persistent collapsed technical activity uses sentence-case headers and stays bounded, redacted, target-fenced, density-aware, free of URL previews, and visually separate from semantic assistant answers. |
|
|
105
105
|
| Assistant rendering | Choose Native Rich Markdown or legacy Markdown-to-HTML for final assistant replies. | Renderer compatibility is explicit instead of being conflated with draft previews. |
|
|
106
|
-
| Bridge UI rendering | Render thinking
|
|
106
|
+
| Bridge UI rendering | Render thinking through ordinary expandable HTML with inline emphasis/code, render tools through native Rich details with JSON code blocks, and keep menus, queue controls, status, settings, diagnostics, and sections on Telegram HTML/plain UI. | Harness-owned surfaces remain operationally predictable and visually distinct from model-authored answers. |
|
|
107
107
|
| Inbound files | Download inbound files to the Pi agent temp directory with size limits. | Screenshots, PDFs, datasets, and artifacts enter Pi as inspectable local files. |
|
|
108
108
|
| Outbound artifacts | Return generated files through `telegram_attach` during active turns or explicit direct delivery. | Agents send real artifacts as files, not pasted blobs. |
|
|
109
109
|
| Voice input | Route audio through configured command-template handlers, programmatic handlers, or STT providers. | Voice notes become usable prompt context. |
|
|
@@ -172,7 +172,7 @@ Messages sent while Pi is busy become queued turns. Priority lanes support contr
|
|
|
172
172
|
|
|
173
173
|
### Native Rich Markdown
|
|
174
174
|
|
|
175
|
-
Rich Markdown is the default model-answer membrane. Complete assistant and guest model replies use Telegram's native Rich Message APIs. Activity thinking
|
|
175
|
+
Rich Markdown is the default model-answer membrane. Complete assistant and guest model replies use Telegram's native Rich Message APIs. Activity thinking uses persistent ordinary HTML with a standard expandable blockquote, while completed tools use native Rich details with JSON code blocks; `thinking`, `tools`, and `verbose` select the visible classes, while menus, status rows, queue controls, settings, diagnostics, and other operational UI retain explicit Telegram HTML/plain rendering. Three Settings controls keep the layers separate: `Draft previews` toggles streamed answer drafts, `Activity` chooses `quiet` or `verbose` technical activity, and `Assistant rendering` chooses final-answer delivery (`rich` Native Rich Markdown or `html` legacy Markdown-to-HTML).
|
|
176
176
|
|
|
177
177
|
### Files And Artifacts
|
|
178
178
|
|
|
@@ -215,7 +215,7 @@ Most controls live in Pi commands or the Telegram menu. Environment variables re
|
|
|
215
215
|
| Inbound file limit | `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES`, `TELEGRAM_MAX_FILE_SIZE_BYTES` |
|
|
216
216
|
| Outbound attachment limit | `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES`, `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES` |
|
|
217
217
|
|
|
218
|
-
Defaults are chosen for ordinary private-bot use: saved config in `~/.pi/agent`, inbound temp files in `~/.pi/agent/tmp/telegram`, `assistant: { rendering: "rich", draftPreviews: false, activity: "
|
|
218
|
+
Defaults are chosen for ordinary private-bot use: saved config in `~/.pi/agent`, inbound temp files in `~/.pi/agent/tmp/telegram`, `assistant: { rendering: "rich", draftPreviews: false, activity: "verbose", timeInjection: "interval" }` for assistant output and activity, and native Telegram active status for long-running turns.
|
|
219
219
|
|
|
220
220
|
## Extension Platform
|
|
221
221
|
|
package/docs/activity.md
CHANGED
|
@@ -180,7 +180,7 @@ Pi provider events use `thinking_*`; the public product term is `reasoning`. The
|
|
|
180
180
|
|
|
181
181
|
Tool activity uses Pi's executed-tool lifecycle (`tool_execution_start/update/end`), not provider `toolcall_*` payloads. Provider tool-call boundaries are used only to classify preceding assistant prose. This prevents duplicate tool rows and reports actual execution results.
|
|
182
182
|
|
|
183
|
-
`args`, `update`, and `result` may contain paths, source text, command output, or other sensitive data. They are available to trusted local extension code but must not be rendered wholesale by default. Core `quiet` mode renders none of them.
|
|
183
|
+
`args`, `update`, and `result` may contain paths, source text, command output, or other sensitive data. They are available to trusted local extension code but must not be rendered wholesale by default. Core `quiet` mode renders none of them. Visible tool modes redact known secret shapes, truncate every evidence field, and place completed tool evidence in native Rich Messages with separate closed Arguments/Update/Result/Error details containing JSON pre blocks.
|
|
184
184
|
|
|
185
185
|
## Delivery Context
|
|
186
186
|
|
|
@@ -291,7 +291,7 @@ The implementation must cover:
|
|
|
291
291
|
|
|
292
292
|
## Core Verbosity And Consumer Policy
|
|
293
293
|
|
|
294
|
-
The bridge owns a global `quiet`/`thinking`/`tools`/`verbose` policy.
|
|
294
|
+
The bridge owns a global `quiet`/`thinking`/`tools`/`verbose` policy. `verbose` is the absent-config default, `thinking` and `tools` select one technical class, and `quiet` disables both. Available thinking uses persistent ordinary HTML with an expandable blockquote; bounded tool evidence uses native Rich details without changing public assistant-segment projection or final replies.
|
|
295
295
|
|
|
296
296
|
The registration and delivery examples above remain the public building blocks for companion-specific policy:
|
|
297
297
|
|
package/docs/architecture.md
CHANGED
|
@@ -256,7 +256,7 @@ At every connected instance `agent_start`, the lifecycle binding starts Telegram
|
|
|
256
256
|
|
|
257
257
|
### Rendering And Delivery
|
|
258
258
|
|
|
259
|
-
Rich Markdown is the default model-answer membrane. Complete assistant replies send final Markdown directly as `InputRichMessage.markdown` through `sendRichMessage` when `assistant.rendering` is `rich`, and through the legacy Markdown-to-HTML renderer when `assistant.rendering` is `html`; guest replies use native Rich Markdown through `InputRichMessageContent` in `answerGuestQuery` results.
|
|
259
|
+
Rich Markdown is the default model-answer membrane. Complete assistant replies send final Markdown directly as `InputRichMessage.markdown` through `sendRichMessage` when `assistant.rendering` is `rich`, and through the legacy Markdown-to-HTML renderer when `assistant.rendering` is `html`; guest replies use native Rich Markdown through `InputRichMessageContent` in `answerGuestQuery` results. Reasoning/thinking blocks, menus, status rows, queue controls, settings, diagnostics, and other harness-owned surfaces stay on explicit Telegram HTML/plain rendering, while completed tool activity uses native Rich block objects for visually distinct structured disclosure. Streaming previews may use `sendRichMessageDraft` only when `assistant.draftPreviews` is enabled and draft delivery succeeds. The bridge still strips top-level assistant action comments before delivery and may split output only for Telegram transport limits.
|
|
260
260
|
|
|
261
261
|
Assistant delivery guarantees:
|
|
262
262
|
|
|
@@ -315,7 +315,7 @@ Queue reactions are shortcut controls for waiting turns. Promotion reactions (`
|
|
|
315
315
|
|
|
316
316
|
When `assistant.proactivePush` is enabled and this instance has exact direct or follower transport authority, completed public assistant text blocks from local/autonomous work are sent once and in source order to the instance's authorized target. Visible commentary/checkpoints and the final block use the configured Rich or HTML renderer. Proactive projection excludes reasoning, tool traffic, token deltas, local prompt text, Telegram-owned turns, and stale generations. Each admitted block remains fenced to its exact target, profile/token stamp, leader epoch or follower registration generation, and session generation; non-idempotent acknowledgement ambiguity never authorizes replay.
|
|
317
317
|
|
|
318
|
-
`assistant.activity` is an independent bridge-owned projection over normalized Activity events.
|
|
318
|
+
`assistant.activity` is an independent bridge-owned projection over normalized Activity events. Each process reloads the shared file-backed setting at `agent-start` before activity admission, so multi-instance mode cannot continue projecting a stale broader process-local selection. Omitted values resolve to `verbose`, while invalid values fail closed to `quiet`; `thinking` and `tools` select one technical class, while `verbose` enables both. Provider-exposed thinking uses persistent ordinary HTML with a standard expandable blockquote. Completed executed tools use native Rich Messages: each sentence-case `🛠` header precedes separate closed `Arguments`, retained `Update N`, and `Result`/`Error` details containing JSON pre blocks, with known-safe Rich rejections falling back to the previous HTML disclosure. Thinking uses a `🧠` header carrying the current Pi thinking level and a bounded redacted latest-text window with inline Markdown rendered as Telegram HTML inside the expandable quote. The projection captures the exact target and transport stamp at activity admission, serializes updates, preserves tool-start order, closes coalescing across assistant/thinking boundaries, bounds retained text/update memory plus edit frames and message/tool size, disables previews and HTTP(S) auto-link recognition inside technical evidence, and never replays a possibly committed send. Session generations own independent queues, so replacement drops queued old work without waiting on an old call. Both proactive prose and active-turn final delivery wait for the admitted activity queue inside their extension-owned delivery tasks, preserving technical-before-semantic ordering without delaying Pi lifecycle completion. Settlement, replacement, disconnect, failure, or stale authority clears only local ownership; already-sent activity messages remain in chat.
|
|
319
319
|
|
|
320
320
|
Telegram prompt guidance is context- and authority-aware. Only an exact direct owner or live registered follower exposes the three pi-telegram model tools, their active-tool metadata, and the compact local bridge suffix. Disconnect or authority loss removes those surfaces for subsequent requests without touching foreign tools; reconnect/recovery restores only the pi-telegram subset that was active before suspension, including across same-process reload. Telegram-originated turns receive a compact pointer to `telegram_help` plus dynamic prompt blocks such as `[voice] delivery: automatic voice`; full voice/button/direct-delivery/Threaded Mode syntax stays in the help tool rather than every system prompt.
|
|
321
321
|
|
package/docs/outbound.md
CHANGED
|
@@ -14,11 +14,11 @@ Proactive blocks use `assistant.rendering` independently of voice policy. Rich m
|
|
|
14
14
|
|
|
15
15
|
## Technical Activity
|
|
16
16
|
|
|
17
|
-
`assistant.activity` defaults to `
|
|
17
|
+
`assistant.activity` defaults to `verbose` when absent and accepts four modes: `quiet`, `thinking`, `tools`, and `verbose`. Explicit stored values remain unchanged; invalid values fail closed to `quiet`. Every Pi instance reloads this shared file-backed mode at `agent-start`, so a setting changed from one thread applies to subsequent runs in the other live instances without requiring process reload. `thinking` shows only provider-exposed thinking, `tools` shows only completed executed tools, and `verbose` shows both without mixing technical UI into assistant Markdown.
|
|
18
18
|
|
|
19
|
-
- Provider-exposed thinking updates one persistent ordinary HTML message. Its `🧠
|
|
20
|
-
- Completed executed tools use
|
|
21
|
-
-
|
|
19
|
+
- Provider-exposed thinking updates one persistent ordinary HTML message. Its `🧠 Thinking: <level>` header reports the current Pi thinking level, such as `high`, above a standard Telegram `<blockquote expandable>` containing a bounded redacted latest-text window whose inline Markdown emphasis and code render as Telegram HTML instead of leaking raw markers. The bridge sends the message once, edits it only as thinking grows, and leaves the disclosure in chat; it never replaces useful density metadata with a generic completion state or uses Rich drafts/Rich Messages. Providers that expose no thinking produce none.
|
|
20
|
+
- Completed executed tools use native `sendRichMessage` block objects. Each tool starts with the sentence-case `🛠 <Tool>: <status>` header, then separate closed `Arguments`, retained `Update N`, and `Result`/`Error` details; every detail contains one preformatted `json` block. Updates remain chronological, dropped-update counts appear in the first retained update summary, and arrays of object entries keep the denser `[{ ... }, { ... }]` layout. A known-safe Rich HTTP 400 rejection falls back once to the previous expandable HTML representation; ambiguous non-idempotent outcomes never replay.
|
|
21
|
+
- Thinking `sendMessage`/`editMessageText` disables link previews and breaks HTTP(S) auto-link recognition inside evidence. Rich tool messages set `skip_entity_detection: true`, so URL-like arguments, updates, and results remain literal code. Consecutive tools coalesce by editing one message only while target, activity, generation, ordering boundary, tool count, and serialized-size bounds still match. Assistant or thinking content closes the batch. A non-idempotent send with unknown commit state is never replayed; failed or ambiguous edits start no fallback send.
|
|
22
22
|
|
|
23
23
|
Thinking retains only a bounded latest-text window, tool updates retain only a bounded latest-entry window, and a session reset abandons queued old-generation work without making the replacement session wait for an old transport call. Final-answer delivery waits for the admitted activity queue inside the extension-owned background delivery task, so completed technical evidence cannot be overtaken by the semantic answer and Pi lifecycle completion remains non-blocking.
|
|
24
24
|
|
|
@@ -54,7 +54,7 @@ The optimization is deliberately narrow. HTML rendering, empty final text, multi
|
|
|
54
54
|
|
|
55
55
|
This behavior does not generate media or alter voice policy. `telegram_attach` still represents an explicit assistant artifact decision, while `hidden`, `mirror`, and `always` continue to decide voice synthesis independently.
|
|
56
56
|
|
|
57
|
-
Core assistant output accepts only the Markdown or HTML `InputRichMessage` forms. Activity thinking
|
|
57
|
+
Core assistant output accepts only the Markdown or HTML `InputRichMessage` forms. Activity thinking remains persistent ordinary HTML with an expandable blockquote, while completed tool evidence uses structured `InputRichMessage.blocks` with closed details and JSON pre blocks. Thinking consumes only lifecycle content actually exposed by the active provider and never synthesizes unavailable private thinking.
|
|
58
58
|
|
|
59
59
|
### Guest Mode media boundary
|
|
60
60
|
|
package/docs/public-api.md
CHANGED
|
@@ -119,10 +119,10 @@ Hidden/default semantics are represented by absence:
|
|
|
119
119
|
|
|
120
120
|
- `threads.automaticCleanup` defaults to `true`; graceful Pi quit deletes the instance's bound Threaded Mode tab without prompting. Set it to `false`, or use `🧹 Thread cleanup` in Telegram Settings, to preserve the tab as a restart hint. Settings views and cleanup reload shared config before evaluating this switch, so another live Pi instance's update takes effect without restarting. Confirmed leader/follower teardown persists an exact target/runtime-generation cleanup intent before Telegram deletion; an interrupted attempt remains retryable by the current or successor leader under current authority and clears only after confirmed deletion. A same-profile replacement leader first adopts any still-active binding and cancels its superseded cleanup, so startup never deletes and recreates a reusable thread. If a follower's graceful envelope is missed, the leader may create the same fenced cleanup only after its heartbeat is stale, the OS confirms the exact registered PID no longer exists, cleanup remains enabled, and no replacement registration can overtake deletion. Heartbeat loss alone, live/unknown process liveness, IPC failure, and auth failure remain non-destructive. Invalid-config recovery makes the setting unresolved and therefore skips destructive cleanup. Manual `/telegram-disconnect` keeps its confirmation and teardown behavior regardless of this setting.
|
|
121
121
|
- `assistant.proactivePush` defaults to `true`; omit it to keep projection enabled, or set it explicitly to `false` to disable it. When enabled, each completed public assistant text block from local or autonomous work is projected to the authorized Telegram target once and in source order. This includes visible intermediate commentary/checkpoints and the final block. It excludes token deltas, hidden reasoning, tool calls/arguments/results, Telegram-owned turns, empty blocks, and stale authority. Projection uses the configured Rich or HTML assistant renderer and binds admitted work to the exact target, profile/token transport generation, direct leader epoch or follower registration generation, and session generation. The old top-level `proactivePush` key is ignored; move the setting manually under `assistant`.
|
|
122
|
-
- `assistant.activity` accepts exactly `"quiet"`, `"thinking"`, `"tools"`, or `"verbose"`
|
|
122
|
+
- `assistant.activity` accepts exactly `"quiet"`, `"thinking"`, `"tools"`, or `"verbose"`; omitted values default to `"verbose"`, explicit values remain unchanged, and invalid values fail closed to `"quiet"`. Each Pi process reloads the shared file-backed value at `agent-start`, so multi-instance activity isolation never relies on a stale process-local config snapshot. `thinking` shows only provider-exposed thinking, `tools` shows only completed tool activity, and `verbose` shows both. Thinking uses persistent ordinary HTML `sendMessage`/`editMessageText` disclosure with a standard expandable blockquote, a `🧠` header carrying the current Pi thinking level, and bounded redacted text whose inline Markdown renders as Telegram HTML. Tools use native Rich Messages with one header followed by separate closed details and JSON pre blocks for bounded redacted arguments, retained updates, and results/errors. Thinking disables link previews on every HTML send/edit and neutralizes HTTP(S) auto-link detection; Rich tool output disables automatic entity detection, with the same protections retained by its HTML fallback. Consecutive tools coalesce only inside the same ordered activity segment and bounded message. Legacy `assistant.activityVerbosity` is read only when `assistant.activity` is absent and is removed by the next Activity Settings write.
|
|
123
123
|
- Voice Reply `hidden`: no `voice.replyMode` key is persisted; legacy `manual` resolves to this silent default. `mirror` adds `[voice] delivery: automatic voice` only to voice/audio-input turns, while `always` adds the same effective line to every Telegram turn.
|
|
124
124
|
- Agent activity status is not configurable in this release. Telegram uses native `sendChatAction(typing)` / product `...active` status as the only automatic in-chat work signal before the final reply.
|
|
125
|
-
- `assistant.timeInjection` accepts `hidden`, `always`, or `interval` and defaults to `
|
|
125
|
+
- `assistant.timeInjection` accepts `hidden`, `always`, or `interval` and defaults to `interval` when absent without migrating an explicit stored value. Settings writes the selected value there, including `hidden`; the old `time.injectionMode` key is ignored and is not migrated. `time.interval` remains the optional interval duration in milliseconds.
|
|
126
126
|
|
|
127
127
|
With `assistant.rendering: "rich"` (the default), assistant Markdown delivery is native: final replies are sent as `InputRichMessage.markdown` via `sendRichMessage`, and draft previews use `sendRichMessageDraft` when a structurally closed preview frame is available. Draft-frame failures are recorded and skipped rather than converted into raw plain preview messages, because partial Markdown can be temporarily invalid while the final answer remains valid. Long native replies are split at Telegram Rich Message transport limits, with oversized fenced code, display-math, and fully wrapped inline-formatting blocks rewrapped per chunk so persisted chunks remain structurally valid. Guest replies use `InputRichMessageContent` in `answerGuestQuery` results. Bridge-owned UI surfaces such as menus, status, queue controls, commands, and sections keep explicit Telegram HTML/plain rendering by default because those texts are authored by the bridge or companion extensions for Telegram UI. Companion extension sections may explicitly request `"markdown"`, `"html"`, or `"plain"` per view. `assistant.rendering: "html"` keeps the compatibility path that converts assistant Markdown to Telegram HTML before ordinary message delivery. The bridge sets `skip_entity_detection: true` for assistant and guest Markdown so technical text such as `/commands`, hashtags, URLs, phone numbers, and card-like numbers does not gain unintended automatic entities; explicit Markdown links still belong in the Markdown source.
|
|
128
128
|
|
package/docs/ui-style.md
CHANGED
|
@@ -35,7 +35,7 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
|
|
|
35
35
|
| `📌` | Proactive push / pinned behavior | Proactive push settings row and detail card | Not generic active/selected state. |
|
|
36
36
|
| `🔬` | Activity / technical detail | Activity settings row and detail card | Chooses quiet, thinking, tools, or verbose bridge activity; not a generic diagnostics marker. |
|
|
37
37
|
| `🧠` | Model thinking activity | Collapsed persistent thinking disclosure summaries | One marker per thinking message; header text reports the current thinking level such as `high`. |
|
|
38
|
-
| `🛠` | Executed tool activity |
|
|
38
|
+
| `🛠` | Executed tool activity | Native Rich tool headers above closed Arguments/Update/Result/Error details | One marker per tool header; tool result state remains text (`done`/`failed`). |
|
|
39
39
|
| `📎` | Attachment | Attachment summaries, queue rows for attachment-only turns | Not for thread binding. |
|
|
40
40
|
|
|
41
41
|
### Command And Control Actions
|
package/index.ts
CHANGED
|
@@ -507,6 +507,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
507
507
|
activityVerbosityRuntime =
|
|
508
508
|
ActivityVerbosity.createTelegramActivityVerbosityRuntime({
|
|
509
509
|
getActivityMode: configControls.getActivityVerbosity,
|
|
510
|
+
refreshActivityMode: configControls.refreshActivityVerbosity,
|
|
510
511
|
getThinkingLevel,
|
|
511
512
|
resolveTarget(event) {
|
|
512
513
|
return event.target ?? proactivePushTargetGetter();
|
|
@@ -515,6 +516,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
515
516
|
isAuthorityActive:
|
|
516
517
|
assistantOutputBindingRuntime.authority.isAuthorityActive,
|
|
517
518
|
sendMessage,
|
|
519
|
+
sendRichMessage,
|
|
518
520
|
editMessageText: editTelegramMessageText,
|
|
519
521
|
recordFailure(operation, event, error) {
|
|
520
522
|
recordRuntimeEvent("activity", error, {
|
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
} from "./rendering.ts";
|
|
12
12
|
import type {
|
|
13
13
|
TelegramEditMessageTextBody,
|
|
14
|
+
TelegramInputRichBlock,
|
|
15
|
+
TelegramInputRichMessage,
|
|
14
16
|
TelegramSendMessageBody,
|
|
17
|
+
TelegramSendRichMessageBody,
|
|
15
18
|
TelegramSentMessage,
|
|
16
19
|
} from "./telegram-api.ts";
|
|
17
20
|
import type { TelegramTarget } from "./target.ts";
|
|
@@ -40,6 +43,7 @@ interface ToolMessage {
|
|
|
40
43
|
messageId: number;
|
|
41
44
|
tools: ToolActivity[];
|
|
42
45
|
target: TelegramTarget;
|
|
46
|
+
format: "rich" | "html";
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
interface ReasoningMessage {
|
|
@@ -164,6 +168,10 @@ function renderThinkingActivityEvidenceHtml(text: string): string {
|
|
|
164
168
|
});
|
|
165
169
|
}
|
|
166
170
|
|
|
171
|
+
function capitalizeActivityLabel(label: string): string {
|
|
172
|
+
return label.length > 0 ? `${label[0]!.toUpperCase()}${label.slice(1)}` : label;
|
|
173
|
+
}
|
|
174
|
+
|
|
167
175
|
function renderToolActivityHtml(tool: ToolActivity): string {
|
|
168
176
|
const evidence = [`"arguments": ${tool.args}`];
|
|
169
177
|
if (tool.droppedUpdates > 0) {
|
|
@@ -183,7 +191,7 @@ function renderToolActivityHtml(tool: ToolActivity): string {
|
|
|
183
191
|
: "done"
|
|
184
192
|
: "running";
|
|
185
193
|
return [
|
|
186
|
-
`<b>${TELEGRAM_TOOL_ACTIVITY_ICON}  ${escapeHtml(tool.name)}:</b> <code>${status}</code>`,
|
|
194
|
+
`<b>${TELEGRAM_TOOL_ACTIVITY_ICON}  ${escapeHtml(capitalizeActivityLabel(tool.name))}:</b> <code>${status}</code>`,
|
|
187
195
|
`<blockquote expandable>${escapeActivityEvidenceHtml(evidence.join("\n\n"))}</blockquote>`,
|
|
188
196
|
].join("\n");
|
|
189
197
|
}
|
|
@@ -194,16 +202,76 @@ export function renderTelegramToolActivityHtml(
|
|
|
194
202
|
return tools.map(renderToolActivityHtml).join("\n\n");
|
|
195
203
|
}
|
|
196
204
|
|
|
205
|
+
function createToolActivityDetail(
|
|
206
|
+
summary: string,
|
|
207
|
+
text: string,
|
|
208
|
+
): TelegramInputRichBlock {
|
|
209
|
+
return {
|
|
210
|
+
type: "details",
|
|
211
|
+
summary: { type: "bold", text: summary },
|
|
212
|
+
blocks: [{ type: "pre", text, language: "json" }],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function renderToolActivityRichBlocks(tool: ToolActivity): TelegramInputRichBlock[] {
|
|
217
|
+
const status = tool.complete
|
|
218
|
+
? tool.isError
|
|
219
|
+
? "failed"
|
|
220
|
+
: "done"
|
|
221
|
+
: "running";
|
|
222
|
+
const blocks: TelegramInputRichBlock[] = [
|
|
223
|
+
{
|
|
224
|
+
type: "paragraph",
|
|
225
|
+
text: [
|
|
226
|
+
{
|
|
227
|
+
type: "bold",
|
|
228
|
+
text: `${TELEGRAM_TOOL_ACTIVITY_ICON} ${capitalizeActivityLabel(tool.name)}:`,
|
|
229
|
+
},
|
|
230
|
+
" ",
|
|
231
|
+
{ type: "code", text: status },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
createToolActivityDetail("Arguments", tool.args),
|
|
235
|
+
];
|
|
236
|
+
tool.updates.forEach((update, index) => {
|
|
237
|
+
const number = tool.droppedUpdates + index + 1;
|
|
238
|
+
const omitted =
|
|
239
|
+
index === 0 && tool.droppedUpdates > 0
|
|
240
|
+
? ` · ${tool.droppedUpdates} earlier omitted`
|
|
241
|
+
: "";
|
|
242
|
+
blocks.push(createToolActivityDetail(`Update ${number}${omitted}`, update));
|
|
243
|
+
});
|
|
244
|
+
if (tool.complete && tool.result !== undefined) {
|
|
245
|
+
blocks.push(
|
|
246
|
+
createToolActivityDetail(tool.isError ? "Error" : "Result", tool.result),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
return blocks;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function renderTelegramToolActivityRichMessage(
|
|
253
|
+
tools: readonly ToolActivity[],
|
|
254
|
+
): TelegramInputRichMessage {
|
|
255
|
+
return {
|
|
256
|
+
blocks: tools.flatMap(renderToolActivityRichBlocks),
|
|
257
|
+
skip_entity_detection: true,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
197
261
|
function toolMessageSize(tools: readonly ToolActivity[]): number {
|
|
198
262
|
return renderTelegramToolActivityHtml(tools).length;
|
|
199
263
|
}
|
|
200
264
|
|
|
265
|
+
function isKnownSafeRichActivityRejection(error: unknown): boolean {
|
|
266
|
+
return error instanceof Error && /HTTP 400: Bad Request:/i.test(error.message);
|
|
267
|
+
}
|
|
268
|
+
|
|
201
269
|
export function renderTelegramThinkingActivityHtml(
|
|
202
270
|
text: string,
|
|
203
271
|
thinkingLevel: string,
|
|
204
272
|
): string {
|
|
205
273
|
return [
|
|
206
|
-
`<b>${TELEGRAM_THINKING_ACTIVITY_ICON} 
|
|
274
|
+
`<b>${TELEGRAM_THINKING_ACTIVITY_ICON}  Thinking:</b> <code>${escapeHtml(thinkingLevel)}</code>`,
|
|
207
275
|
`<blockquote expandable>${renderThinkingActivityEvidenceHtml(text)}</blockquote>`,
|
|
208
276
|
].join("\n");
|
|
209
277
|
}
|
|
@@ -217,16 +285,25 @@ export interface TelegramActivityVerbosityRuntime {
|
|
|
217
285
|
|
|
218
286
|
export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
219
287
|
getActivityMode: () => "quiet" | "thinking" | "tools" | "verbose";
|
|
288
|
+
refreshActivityMode?: () => Promise<void>;
|
|
220
289
|
getThinkingLevel: () => string;
|
|
221
290
|
resolveTarget: (event: TelegramActivityEvent) => TelegramTarget | undefined;
|
|
222
291
|
captureAuthority: () => TAuthority;
|
|
223
292
|
isAuthorityActive: (authority: TAuthority) => boolean;
|
|
224
293
|
sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
|
|
294
|
+
sendRichMessage: (
|
|
295
|
+
body: TelegramSendRichMessageBody,
|
|
296
|
+
) => Promise<TelegramSentMessage>;
|
|
225
297
|
editMessageText: (
|
|
226
298
|
body: TelegramEditMessageTextBody,
|
|
227
299
|
) => Promise<"edited" | "unchanged">;
|
|
228
300
|
recordFailure?: (
|
|
229
|
-
operation:
|
|
301
|
+
operation:
|
|
302
|
+
| "config-refresh"
|
|
303
|
+
| "reasoning-send"
|
|
304
|
+
| "reasoning-edit"
|
|
305
|
+
| "tool-send"
|
|
306
|
+
| "tool-edit",
|
|
230
307
|
event: TelegramActivityEvent,
|
|
231
308
|
error: unknown,
|
|
232
309
|
) => void;
|
|
@@ -361,31 +438,70 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
361
438
|
try {
|
|
362
439
|
if (canAppend && toolMessage) {
|
|
363
440
|
const nextTools = [...toolMessage.tools, tool];
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
441
|
+
try {
|
|
442
|
+
await deps.editMessageText({
|
|
443
|
+
chat_id: target.chatId,
|
|
444
|
+
message_id: toolMessage.messageId,
|
|
445
|
+
...(toolMessage.format === "rich"
|
|
446
|
+
? {
|
|
447
|
+
rich_message:
|
|
448
|
+
renderTelegramToolActivityRichMessage(nextTools),
|
|
449
|
+
}
|
|
450
|
+
: {
|
|
451
|
+
text: renderTelegramToolActivityHtml(nextTools),
|
|
452
|
+
parse_mode: "HTML" as const,
|
|
453
|
+
link_preview_options: { is_disabled: true },
|
|
454
|
+
}),
|
|
455
|
+
});
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (
|
|
458
|
+
toolMessage.format !== "rich" ||
|
|
459
|
+
!isKnownSafeRichActivityRejection(error)
|
|
460
|
+
) {
|
|
461
|
+
throw error;
|
|
462
|
+
}
|
|
463
|
+
await deps.editMessageText({
|
|
464
|
+
chat_id: target.chatId,
|
|
465
|
+
message_id: toolMessage.messageId,
|
|
466
|
+
text: renderTelegramToolActivityHtml(nextTools),
|
|
467
|
+
parse_mode: "HTML",
|
|
468
|
+
link_preview_options: { is_disabled: true },
|
|
469
|
+
});
|
|
470
|
+
toolMessage.format = "html";
|
|
471
|
+
}
|
|
371
472
|
if (generation !== acceptedGeneration) return;
|
|
372
473
|
toolMessage.tools = nextTools;
|
|
373
474
|
return;
|
|
374
475
|
}
|
|
375
|
-
const
|
|
476
|
+
const body = {
|
|
376
477
|
chat_id: target.chatId,
|
|
377
478
|
...(target.threadId === undefined
|
|
378
479
|
? {}
|
|
379
480
|
: { message_thread_id: target.threadId }),
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
481
|
+
};
|
|
482
|
+
let sent: TelegramSentMessage;
|
|
483
|
+
let format: ToolMessage["format"] = "rich";
|
|
484
|
+
try {
|
|
485
|
+
sent = await deps.sendRichMessage({
|
|
486
|
+
...body,
|
|
487
|
+
rich_message: renderTelegramToolActivityRichMessage([tool]),
|
|
488
|
+
});
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (!isKnownSafeRichActivityRejection(error)) throw error;
|
|
491
|
+
sent = await deps.sendMessage({
|
|
492
|
+
...body,
|
|
493
|
+
text: renderTelegramToolActivityHtml([tool]),
|
|
494
|
+
parse_mode: "HTML",
|
|
495
|
+
link_preview_options: { is_disabled: true },
|
|
496
|
+
});
|
|
497
|
+
format = "html";
|
|
498
|
+
}
|
|
384
499
|
if (generation !== acceptedGeneration) return;
|
|
385
500
|
toolMessage = {
|
|
386
501
|
messageId: sent.message_id,
|
|
387
502
|
tools: [tool],
|
|
388
503
|
target: { ...target },
|
|
504
|
+
format,
|
|
389
505
|
};
|
|
390
506
|
} catch (error) {
|
|
391
507
|
deps.recordFailure?.(canAppend ? "tool-edit" : "tool-send", event, error);
|
|
@@ -396,6 +512,16 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
396
512
|
event: TelegramActivityEvent,
|
|
397
513
|
acceptedGeneration: number,
|
|
398
514
|
) => {
|
|
515
|
+
if (event.type === "agent-start" && deps.refreshActivityMode) {
|
|
516
|
+
try {
|
|
517
|
+
await deps.refreshActivityMode();
|
|
518
|
+
} catch (error) {
|
|
519
|
+
deps.recordFailure?.("config-refresh", event, error);
|
|
520
|
+
clearActivity();
|
|
521
|
+
activityId = event.activityId;
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
399
525
|
if (!ensureActivity(event)) {
|
|
400
526
|
if (
|
|
401
527
|
activityId === event.activityId &&
|
package/lib/config.ts
CHANGED
|
@@ -769,10 +769,19 @@ export function createTelegramActivityVerbosityGetter(
|
|
|
769
769
|
}
|
|
770
770
|
return "quiet";
|
|
771
771
|
}
|
|
772
|
-
|
|
772
|
+
if (assistant?.activityVerbosity !== undefined) {
|
|
773
|
+
return assistant.activityVerbosity === "verbose" ? "verbose" : "quiet";
|
|
774
|
+
}
|
|
775
|
+
return "verbose";
|
|
773
776
|
};
|
|
774
777
|
}
|
|
775
778
|
|
|
779
|
+
export function createTelegramActivityVerbosityRefresher(
|
|
780
|
+
configStore: TelegramMutableConfigStore,
|
|
781
|
+
): () => Promise<void> {
|
|
782
|
+
return () => loadLatestTelegramConfig(configStore);
|
|
783
|
+
}
|
|
784
|
+
|
|
776
785
|
export function createTelegramActivityVerbositySetter(
|
|
777
786
|
configStore: TelegramMutableConfigStore,
|
|
778
787
|
): (verbosity: TelegramActivityVerbosity) => Promise<void> {
|
|
@@ -848,9 +857,11 @@ export function resolveTelegramTimeConfig(
|
|
|
848
857
|
timeInjection: TelegramTimeMode | undefined = undefined,
|
|
849
858
|
): ResolvedTelegramTimeConfig {
|
|
850
859
|
const injectionMode: TelegramTimeMode =
|
|
851
|
-
timeInjection ===
|
|
852
|
-
?
|
|
853
|
-
: "
|
|
860
|
+
timeInjection === undefined
|
|
861
|
+
? "interval"
|
|
862
|
+
: timeInjection === "always" || timeInjection === "interval"
|
|
863
|
+
? timeInjection
|
|
864
|
+
: "hidden";
|
|
854
865
|
const interval =
|
|
855
866
|
typeof raw?.interval === "number" && raw.interval > 0
|
|
856
867
|
? raw.interval
|
|
@@ -976,6 +987,8 @@ export function createTelegramConfigControls(
|
|
|
976
987
|
createTelegramAssistantRenderingModeSetter(configStore),
|
|
977
988
|
getActivityVerbosity:
|
|
978
989
|
createTelegramActivityVerbosityGetter(configStore),
|
|
990
|
+
refreshActivityVerbosity:
|
|
991
|
+
createTelegramActivityVerbosityRefresher(configStore),
|
|
979
992
|
setActivityVerbosity:
|
|
980
993
|
createTelegramActivityVerbositySetter(configStore),
|
|
981
994
|
getVoiceReplyMode: createTelegramVoiceReplyModeGetter(configStore),
|
package/lib/menu-settings.ts
CHANGED
|
@@ -228,10 +228,10 @@ export function buildActivityVerbositySettingsText(
|
|
|
228
228
|
"",
|
|
229
229
|
"Choose how much technical model activity Telegram shows.",
|
|
230
230
|
"",
|
|
231
|
-
"<code>-</code> <code>quiet</code
|
|
231
|
+
"<code>-</code> <code>quiet</code>: show no thinking or tool traffic.",
|
|
232
232
|
"<code>-</code> <code>thinking</code>: show persistent collapsed thinking.",
|
|
233
|
-
"<code>-</code> <code>tools</code>: show persistent
|
|
234
|
-
"<code>-</code> <code>verbose</code
|
|
233
|
+
"<code>-</code> <code>tools</code>: show persistent Rich tool details.",
|
|
234
|
+
"<code>-</code> <code>verbose</code> (default): show both thinking and tools.",
|
|
235
235
|
].join("\n");
|
|
236
236
|
}
|
|
237
237
|
|
|
@@ -260,9 +260,9 @@ export function buildTimeInjectionModeSettingsText(
|
|
|
260
260
|
"",
|
|
261
261
|
"Controls whether Telegram-originated prompts include a compact wall-clock [time] line.",
|
|
262
262
|
"",
|
|
263
|
-
"<code>-</code> <code>hidden</code
|
|
263
|
+
"<code>-</code> <code>hidden</code>: no time line is added to prompt context.",
|
|
264
264
|
"<code>-</code> <code>always</code>: add time to every Telegram turn.",
|
|
265
|
-
"<code>-</code> <code>interval</code
|
|
265
|
+
"<code>-</code> <code>interval</code> (default): add time at most once per chat interval (1 hour unless configured).",
|
|
266
266
|
].join("\n");
|
|
267
267
|
}
|
|
268
268
|
|
package/lib/telegram-api.ts
CHANGED
|
@@ -285,6 +285,7 @@ export type TelegramRichText =
|
|
|
285
285
|
| { type: "bold" | "code"; text: TelegramRichText };
|
|
286
286
|
|
|
287
287
|
export type TelegramInputRichBlock =
|
|
288
|
+
| { type: "paragraph"; text: TelegramRichText }
|
|
288
289
|
| { type: "pre"; text: TelegramRichText; language?: string }
|
|
289
290
|
| {
|
|
290
291
|
type: "details";
|