@llblab/pi-telegram 0.25.0 → 0.25.2
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 +4 -13
- package/CHANGELOG.md +9 -0
- package/README.md +6 -6
- package/docs/activity.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/outbound.md +11 -11
- package/docs/public-api.md +4 -4
- package/docs/ui-style.md +3 -2
- package/index.ts +1 -4
- package/lib/activity-verbosity.ts +172 -86
- package/lib/config.ts +35 -18
- package/lib/menu-settings.ts +16 -4
- package/lib/telegram-api.ts +6 -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
|
-
-
|
|
92
|
+
- Core thinking/tool visibility is controlled by `assistant.activity: "quiet" | "thinking" | "tools" | "verbose"`. It remains separate from proactive public prose and assistant Markdown: provider-exposed thinking and completed executed tools use durable ordinary HTML messages with standard expandable blockquotes and never use Rich Messages. Thinking uses the `🧠` header, tools use `🛠`, `verbose` enables both, and every activity send/edit disables Telegram link previews. 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
|
|
124
|
+
- Optional Telegram wall-clock context may add `[time] YYYY-MM-DD HH:mm:ss <timezone>` to Telegram-originated prompts. It is hidden by default, 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,24 +2,15 @@
|
|
|
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
|
-
## P1 —
|
|
5
|
+
## P1 — Configurable Activity Live Smoke
|
|
6
6
|
|
|
7
|
-
Context:
|
|
8
|
-
|
|
9
|
-
Bot API evidence: Bot API 10.2 exposes explicit outgoing Rich Message blocks. `InputRichBlockThinking` is draft-only and disappears when the draft lifecycle ends; `InputRichBlockDetails` provides a collapsed disclosure container, and `InputRichBlockPreformatted` provides nested `<pre><code>` content. Exact mobile/Desktop rendering, edit behavior, and practical limits remain live-verification gates.
|
|
7
|
+
Context: configurable activity now offers quiet, thinking-only, tools-only, and verbose modes. Thinking and tools use persistent collapsed ordinary HTML messages instead of draft-only Rich Thinking blocks.
|
|
10
8
|
|
|
11
9
|
Open work:
|
|
12
10
|
|
|
13
|
-
- [
|
|
14
|
-
- [x] In `verbose`, stream reasoning content exposed by supported Pi lifecycle events through a short-lived Telegram Rich Draft `Thinking` block bound to the exact chat/thread, profile, session generation, and direct-owner or follower authority. Finalization, cancellation, failure, replacement, and disconnect must clear local draft ownership without creating a persisted reasoning message or projecting unavailable provider-private chain-of-thought.
|
|
15
|
-
- [x] Persist completed tool activity as bridge-owned compact ordinary HTML messages rather than assistant Markdown or Rich Messages. Render each tool call under one registered semantic icon and tool name with bounded, escaped arguments, updates, result, and error evidence inside a standard expandable blockquote.
|
|
16
|
-
- [x] Coalesce consecutive tool calls from one ordered run into the same editable ordinary message while it remains within Telegram HTML message limits, with one independent expandable blockquote per call. Start a new message when the current message cannot safely accept another block; never reorder tools across assistant prose, reasoning boundaries, targets, runs, or lifecycle generations.
|
|
17
|
-
- [x] Define bounded output, redaction, truncation, retry, edit-ambiguity, and transport-failure behavior so verbose mode cannot leak known secrets, flood the target, replay a possibly committed message, block Pi lifecycle completion, or disturb quiet-mode preview/final ordering. Record failures through existing diagnostics.
|
|
18
|
-
- [x] Reconcile the current no-hidden-reasoning invariant and Rich rendering boundary in `AGENTS.md`, then update README, Settings/configuration docs, architecture/outbound docs, public activity guidance, UI emoji registry, and focused config/menu/activity/delivery/rendering/integration regressions.
|
|
19
|
-
- [x] Order first-level `Settings` by operator meaning while keeping `⬆️ Main menu` first: message presentation (`Draft previews`, `Rendering`, `Voice reply`), technical/public activity (`Activity`, `Proactive push`), prompt context (`Time injection`), then lifecycle (`Thread cleanup`). Append extension-provided rows using their explicit `settings.order` and stable section-id tie-break, preserve each detail submenu's semantic option order, and add focused menu/section regressions.
|
|
20
|
-
- [ ] Live-smoke both modes in classic and Threaded Mode on Telegram mobile and Desktop, including multiple sequential tools, oversized output rollover, cancellation during reasoning, tool failure, session replacement, follower transport, and final-answer ordering.
|
|
11
|
+
- [ ] Live-smoke all four modes in classic and Threaded Mode on Telegram mobile and Desktop, including progressive thinking edits, multiple sequential tools, oversized output rollover, cancellation during thinking, tool failure, session replacement, follower transport, and final-answer ordering.
|
|
21
12
|
|
|
22
|
-
Done when:
|
|
13
|
+
Done when: live mobile and Desktop clients confirm the intended mode isolation, persistent disclosures, routing, rollover, cancellation, failure, and final-answer ordering behavior in classic and Threaded Mode.
|
|
23
14
|
|
|
24
15
|
## P1 — Native Windows Runtime Smoke
|
|
25
16
|
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.25.2: Persistent Thinking Activity Hotfix
|
|
4
|
+
|
|
5
|
+
- `Activity Modes`: Expanded `assistant.activity` to `quiet`, `thinking`, `tools`, and `verbose`; thinking now uses persistent ordinary HTML messages with a `🧠` header and collapsed bounded disclosure instead of ephemeral Rich drafts, while tools retain `🛠` disclosures. Every activity send/edit disables Telegram link previews. Impact: operators can inspect thinking-only, tools-only, or combined technical history without Telegram removing thinking when the final answer arrives or URL-like evidence expanding into distracting previews.
|
|
6
|
+
|
|
7
|
+
## 0.25.1: Config And Activity Presentation
|
|
8
|
+
|
|
9
|
+
- `Time Injection Config`: Moved the Settings-owned mode to `assistant.timeInjection`, while retaining `time.interval` as the independent interval duration. The old `time.injectionMode` key is ignored without fallback, migration, or automatic rewriting. Impact: assistant presentation controls share one config domain without silently mutating historical user data.
|
|
10
|
+
- `Verbose Tool Evidence`: Quoted argument, update, result, and error labels as JSON-style properties and compacted arrays of object entries to `[{ ... }, { ... }]` while retaining readable fields, evidence order, redaction, truncation, and delivery bounds. Impact: multi-entry tool updates consume less vertical space without losing technical evidence.
|
|
11
|
+
|
|
3
12
|
## 0.25.0: Configurable Activity
|
|
4
13
|
|
|
5
14
|
- `Activity`: Added a persisted quiet-by-default `assistant.activity` Settings control. Verbose mode streams only provider-exposed reasoning through ephemeral target-fenced Rich Thinking drafts, projects inline Markdown bold/code markers to native Rich Text, and records completed executed tools through memory/transport-bounded ordinary HTML messages with standard expandable blockquotes; tool activity never uses Rich Messages. Tool headers use `🛠 <tool>: <status>` with a monospace `running`, `done`, or `failed` status, while argument, update, result, and error labels sit directly before their serialized values (`arguments: {`, `result: {`) for compact scanning. Settings refreshes always read the live value; legacy `assistant.activityVerbosity` is accepted only when the canonical key is absent and removed on the next write. Consecutive ordered tools coalesce without crossing assistant/reasoning, target, activity, or transport-generation boundaries. Final delivery waits behind admitted technical activity inside the background delivery task, while replacement sessions abandon queued old-generation work immediately. Impact: operators can inspect compact redacted technical activity without stale menu state, mixing it into assistant answers, exposing it by default, replaying ambiguous sends, reordering the final answer, or delaying Pi lifecycle completion.
|
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,
|
|
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 quiet-by-default `Activity` setting can persist collapsed provider-exposed thinking, tool evidence, or both. 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
|
|
|
@@ -98,12 +98,12 @@ The first Telegram user to message the bot becomes the allowed owner. Other user
|
|
|
98
98
|
| Queue control | Inspect waiting turns, delete stale work, promote important prompts, continue, abort, stop, or force the next queued item. | Long Pi tasks keep running while new mobile prompts stay visible and controllable instead of interrupting or disappearing. |
|
|
99
99
|
| Operator menu | Use `/start` for status, prompt templates, model, thinking, settings, queue, extension sections, and diagnostics. | The bot is an operator panel, not a command cheat sheet. |
|
|
100
100
|
| Prompt templates | Run Pi prompt templates as Telegram-safe commands such as `/fix_tests`. | Reusable local workflows become phone-accessible without exposing arbitrary terminal commands. |
|
|
101
|
-
| Model and thinking | Switch model or
|
|
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 `quiet` answer-only surface, or select `verbose` for
|
|
104
|
+
| Activity | Keep the default `quiet` answer-only surface, show only `thinking`, show only `tools`, or select `verbose` for both. | Persistent collapsed technical activity stays bounded, redacted, target-fenced, 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
|
|
106
|
+
| Bridge UI rendering | Render thinking and tool activity through ordinary expandable HTML messages, and menus, queue controls, status, settings, diagnostics, and sections through 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.
|
|
175
|
+
Rich Markdown is the default model-answer membrane. Complete assistant and guest model replies use Telegram's native Rich Message APIs. Activity thinking and completed tools use persistent ordinary HTML messages with standard expandable blockquotes and never Rich Messages; `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
|
|
|
@@ -225,7 +225,7 @@ Companion extensions can integrate with Telegram without owning polling or trans
|
|
|
225
225
|
- Add menu sections and settings surfaces.
|
|
226
226
|
- Add compact status rows.
|
|
227
227
|
- Deliver target-aware operational views and chat actions from companion code.
|
|
228
|
-
- Observe normalized assistant,
|
|
228
|
+
- Observe normalized assistant, thinking, tool, compaction, and settlement activity without blocking Pi.
|
|
229
229
|
- Handle update/callback namespaces.
|
|
230
230
|
- Provide inbound preprocessing handlers.
|
|
231
231
|
- Provide outbound voice synthesis.
|
package/docs/activity.md
CHANGED
|
@@ -291,7 +291,7 @@ The implementation must cover:
|
|
|
291
291
|
|
|
292
292
|
## Core Verbosity And Consumer Policy
|
|
293
293
|
|
|
294
|
-
The bridge
|
|
294
|
+
The bridge owns a global `quiet`/`thinking`/`tools`/`verbose` policy. Quiet is the default, `thinking` and `tools` select one technical class, and `verbose` enables both. Available thinking and bounded tool evidence use persistent ordinary HTML messages with expandable blockquotes 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
|
@@ -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. Omitted or invalid values resolve to `quiet`, which emits no
|
|
318
|
+
`assistant.activity` is an independent bridge-owned projection over normalized Activity events. Omitted or invalid values resolve to `quiet`, which emits no thinking or tool traffic; `thinking` and `tools` select one technical class, while `verbose` enables both. Provider-exposed thinking and completed executed tools use persistent ordinary HTML messages with standard expandable blockquotes and never use Rich Messages. Thinking uses a `🧠` header and bounded redacted latest-text window; tools use `🛠` headers and bounded redacted execution evidence. 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, 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
|
@@ -12,26 +12,26 @@ Proactive projection defaults on. With `assistant.proactivePush` omitted or set
|
|
|
12
12
|
|
|
13
13
|
Proactive blocks use `assistant.rendering` independently of voice policy. Rich mode sends native Rich Markdown and HTML mode keeps the established HTML renderer; proactive projection does not synthesize voice or attach queued files merely because Rich rendering is active. The queue revalidates exact target, profile/token transport generation, leader epoch or follower registration generation, and session generation before each send. Telegram-owned turns remain on their ordinary reply path, and `commit-unknown` never permits proactive replay.
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Technical Activity
|
|
16
16
|
|
|
17
|
-
`assistant.activity` defaults to `quiet
|
|
17
|
+
`assistant.activity` defaults to `quiet` and accepts four modes: `quiet`, `thinking`, `tools`, and `verbose`. `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
|
|
20
|
-
- Completed executed tools use ordinary `sendMessage` HTML, never `sendRichMessage`. Each tool header renders as `🛠 <tool>: <status>`, with a stable two-space visual gap after the icon and the `running`, `done`, or `failed` status in monospace. One standard Telegram `<blockquote expandable>` then contains bounded redacted evidence in chronological order.
|
|
21
|
-
- Consecutive tools coalesce by editing one message only while target, activity, generation, ordering boundary, tool count, and serialized-size bounds still match. Assistant or
|
|
19
|
+
- Provider-exposed thinking updates one persistent ordinary HTML message. Its `🧠 thinking: running|done` header sits above a standard Telegram `<blockquote expandable>` containing a bounded redacted latest-text window. The bridge sends the message once, edits it as thinking grows, and leaves the completed disclosure in chat; it never uses Rich drafts or Rich Messages. Providers that expose no thinking produce none.
|
|
20
|
+
- Completed executed tools use ordinary `sendMessage` HTML, never `sendRichMessage`. Each tool header renders as `🛠 <tool>: <status>`, with a stable two-space visual gap after the icon and the `running`, `done`, or `failed` status in monospace. One standard Telegram `<blockquote expandable>` then contains bounded redacted evidence in chronological order. JSON-style labels share a line with their serialized value opening, for example `"arguments": {`, `"update 1": {`, and `"result": {` or `"error": {`; arrays of object entries use the denser `[{ ... }, { ... }]` layout while retaining indented fields.
|
|
21
|
+
- Every thinking/tool `sendMessage` and `editMessageText` sets `link_preview_options.is_disabled: true`, so URL-like evidence never expands into a web preview. 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
|
|
|
25
|
-
|
|
25
|
+
Technical activity is operational evidence, not part of the semantic answer stream. Final-answer rendering, voice policy, artifacts, and quiet behavior remain unchanged.
|
|
26
26
|
|
|
27
27
|
### Live Activity Smoke
|
|
28
28
|
|
|
29
29
|
Exercise one classic chat and one Threaded Mode follower target on both Telegram mobile and Desktop:
|
|
30
30
|
|
|
31
|
-
1. Leave `Activity` on `quiet`; run a
|
|
32
|
-
2.
|
|
31
|
+
1. Leave `Activity` on `quiet`; run a thinking-capable request with two tools and confirm only normal preview/public/final output appears in established order.
|
|
32
|
+
2. Exercise `thinking`, `tools`, and `verbose`; confirm persistent collapsed thinking appears only in the modes that include it, two sequential tools become two independently collapsed disclosures in one message only in the modes that include tools, and the final answer follows admitted technical UI.
|
|
33
33
|
3. Produce oversized arguments, multiple updates, and oversized result/error output; confirm redaction/truncation markers, latest-update retention, and safe rollover to another message without flood or reordering.
|
|
34
|
-
4. Cancel during
|
|
34
|
+
4. Cancel during thinking, fail a tool, replace the Pi session, and disconnect/reconnect transport; confirm already-sent thinking remains in chat, stale activity does not cross generations, failure remains collapsed technical evidence, and subsequent work uses only the current target/authority.
|
|
35
35
|
5. Repeat through a registered follower and confirm every draft/send/edit stays in its assigned thread. Capture client/version, mode, target role, observed block behavior, and any Bot API/runtime diagnostic for each result.
|
|
36
36
|
|
|
37
37
|
## Standard
|
|
@@ -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.
|
|
57
|
+
Core assistant output accepts only the Markdown or HTML `InputRichMessage` forms. Activity thinking and tool evidence use persistent ordinary HTML messages with expandable blockquotes and never Rich Message 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
|
@@ -95,14 +95,14 @@ interface TelegramConfig {
|
|
|
95
95
|
draftPreviews?: boolean;
|
|
96
96
|
rendering?: "rich" | "html";
|
|
97
97
|
proactivePush?: boolean;
|
|
98
|
-
activity?: "quiet" | "verbose";
|
|
98
|
+
activity?: "quiet" | "thinking" | "tools" | "verbose";
|
|
99
|
+
timeInjection?: "hidden" | "always" | "interval";
|
|
99
100
|
};
|
|
100
101
|
voice?: {
|
|
101
102
|
replyMode?: "hidden" | "mirror" | "always";
|
|
102
103
|
sendTranscript?: boolean;
|
|
103
104
|
};
|
|
104
105
|
time?: {
|
|
105
|
-
injectionMode?: "hidden" | "always" | "interval";
|
|
106
106
|
interval?: number;
|
|
107
107
|
};
|
|
108
108
|
threads?: {
|
|
@@ -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. 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"` or `"verbose"` and resolves omitted or invalid values to `"quiet"`.
|
|
122
|
+
- `assistant.activity` accepts exactly `"quiet"`, `"thinking"`, `"tools"`, or `"verbose"` and resolves omitted or invalid values to `"quiet"`. `thinking` shows only provider-exposed thinking, `tools` shows only completed tool activity, and `verbose` shows both. Thinking and tools use persistent ordinary HTML `sendMessage`/`editMessageText` disclosures with standard expandable blockquotes; thinking carries a `🧠` header and bounded redacted text, while each tool contains bounded redacted arguments, retained updates, results, and errors. Activity output never uses Rich Messages and disables link previews on every send/edit. 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
|
-
-
|
|
125
|
+
- `assistant.timeInjection` accepts `hidden`, `always`, or `interval` and defaults to `hidden`. 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
|
@@ -33,8 +33,9 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
|
|
|
33
33
|
| `👄` | Voice reply policy | Voice reply settings row and detail card | Not a generic audio attachment marker. |
|
|
34
34
|
| `🕒` | Time injection / wall-clock context | Time injection settings row and detail card | Clock-face marker with hands; not a generic duration/progress marker. |
|
|
35
35
|
| `📌` | Proactive push / pinned behavior | Proactive push settings row and detail card | Not generic active/selected state. |
|
|
36
|
-
| `🔬` | Activity / technical detail | Activity settings row and detail card | Chooses quiet
|
|
37
|
-
|
|
|
36
|
+
| `🔬` | Activity / technical detail | Activity settings row and detail card | Chooses quiet, thinking, tools, or verbose bridge activity; not a generic diagnostics marker. |
|
|
37
|
+
| `🧠` | Model thinking activity | Collapsed persistent thinking disclosure summaries | One marker per thinking message; completion state remains text (`running`/`done`). |
|
|
38
|
+
| `🛠` | Executed tool activity | Collapsed tool disclosure summaries | One marker per tool summary; tool result state remains text (`done`/`failed`). |
|
|
38
39
|
| `📎` | Attachment | Attachment summaries, queue rows for attachment-only turns | Not for thread binding. |
|
|
39
40
|
|
|
40
41
|
### Command And Control Actions
|
package/index.ts
CHANGED
|
@@ -506,9 +506,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
506
506
|
const assistantOutputRuntime = assistantOutputBindingRuntime.runtime;
|
|
507
507
|
activityVerbosityRuntime =
|
|
508
508
|
ActivityVerbosity.createTelegramActivityVerbosityRuntime({
|
|
509
|
-
|
|
510
|
-
return configControls.getActivityVerbosity() === "verbose";
|
|
511
|
-
},
|
|
509
|
+
getActivityMode: configControls.getActivityVerbosity,
|
|
512
510
|
resolveTarget(event) {
|
|
513
511
|
return event.target ?? proactivePushTargetGetter();
|
|
514
512
|
},
|
|
@@ -516,7 +514,6 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
516
514
|
isAuthorityActive:
|
|
517
515
|
assistantOutputBindingRuntime.authority.isAuthorityActive,
|
|
518
516
|
sendMessage,
|
|
519
|
-
sendRichMessageDraft,
|
|
520
517
|
editMessageText: editTelegramMessageText,
|
|
521
518
|
recordFailure(operation, event, error) {
|
|
522
519
|
recordRuntimeEvent("activity", error, {
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bridge-owned Telegram activity verbosity projection
|
|
3
3
|
* Zones: telegram activity, rich rendering, operational delivery
|
|
4
|
-
* Owns
|
|
4
|
+
* Owns persistent bounded thinking and tool disclosures; excludes activity normalization, assistant answer rendering, and transport authority policy
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { TelegramActivityEvent } from "./activity.ts";
|
|
8
8
|
import { escapeHtml } from "./rendering.ts";
|
|
9
9
|
import type {
|
|
10
10
|
TelegramEditMessageTextBody,
|
|
11
|
-
TelegramRichText,
|
|
12
11
|
TelegramSendMessageBody,
|
|
13
|
-
TelegramSendRichMessageDraftBody,
|
|
14
12
|
TelegramSentMessage,
|
|
15
13
|
} from "./telegram-api.ts";
|
|
16
14
|
import type { TelegramTarget } from "./target.ts";
|
|
17
15
|
|
|
18
16
|
export const TELEGRAM_TOOL_ACTIVITY_ICON = "🛠";
|
|
17
|
+
export const TELEGRAM_THINKING_ACTIVITY_ICON = "🧠";
|
|
19
18
|
export const TELEGRAM_ACTIVITY_DETAIL_MAX_CHARS = 1_200;
|
|
20
19
|
export const TELEGRAM_ACTIVITY_MESSAGE_MAX_CHARS = 3_900;
|
|
21
20
|
export const TELEGRAM_ACTIVITY_MESSAGE_MAX_TOOLS = 6;
|
|
22
|
-
export const
|
|
21
|
+
export const TELEGRAM_REASONING_MESSAGE_MAX_FRAMES = 24;
|
|
23
22
|
export const TELEGRAM_REASONING_BUFFER_MAX_CHARS = 1_200;
|
|
24
23
|
export const TELEGRAM_TOOL_UPDATE_MAX_ENTRIES = 4;
|
|
25
24
|
|
|
@@ -40,6 +39,11 @@ interface ToolMessage {
|
|
|
40
39
|
target: TelegramTarget;
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
interface ReasoningMessage {
|
|
43
|
+
messageId: number;
|
|
44
|
+
target: TelegramTarget;
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
function targetEquals(left: TelegramTarget, right: TelegramTarget): boolean {
|
|
44
48
|
return left.chatId === right.chatId && left.threadId === right.threadId;
|
|
45
49
|
}
|
|
@@ -57,40 +61,71 @@ function redactActivityText(text: string): string {
|
|
|
57
61
|
);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
64
|
+
function formatActivityJson(value: unknown, depth = 0): string[] {
|
|
65
|
+
const indent = " ".repeat(depth);
|
|
66
|
+
if (Array.isArray(value)) {
|
|
67
|
+
if (value.length === 0) return [`${indent}[]`];
|
|
68
|
+
if (
|
|
69
|
+
value.every(
|
|
70
|
+
(entry) =>
|
|
71
|
+
entry !== null && typeof entry === "object" && !Array.isArray(entry),
|
|
72
|
+
)
|
|
73
|
+
) {
|
|
74
|
+
const lines = [`${indent}[{`];
|
|
75
|
+
value.forEach((entry, index) => {
|
|
76
|
+
const fields = Object.entries(entry as Record<string, unknown>);
|
|
77
|
+
fields.forEach(([key, nested], fieldIndex) => {
|
|
78
|
+
const nestedLines = formatActivityJson(nested, depth + 1);
|
|
79
|
+
const nestedIndent = " ".repeat(depth + 1);
|
|
80
|
+
lines.push(
|
|
81
|
+
`${nestedIndent}${JSON.stringify(key)}: ${nestedLines[0]!.slice(nestedIndent.length)}`,
|
|
82
|
+
...nestedLines.slice(1),
|
|
83
|
+
);
|
|
84
|
+
if (fieldIndex < fields.length - 1) {
|
|
85
|
+
lines[lines.length - 1] += ",";
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
lines.push(
|
|
89
|
+
index < value.length - 1 ? `${indent}}, {` : `${indent}}]`,
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
return lines;
|
|
78
93
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
94
|
+
const lines = [`${indent}[`];
|
|
95
|
+
value.forEach((entry, index) => {
|
|
96
|
+
const nestedLines = formatActivityJson(entry, depth + 1);
|
|
97
|
+
if (index < value.length - 1) {
|
|
98
|
+
nestedLines[nestedLines.length - 1] += ",";
|
|
99
|
+
}
|
|
100
|
+
lines.push(...nestedLines);
|
|
82
101
|
});
|
|
83
|
-
|
|
102
|
+
lines.push(`${indent}]`);
|
|
103
|
+
return lines;
|
|
84
104
|
}
|
|
85
|
-
if (
|
|
86
|
-
|
|
105
|
+
if (value !== null && typeof value === "object") {
|
|
106
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
107
|
+
if (entries.length === 0) return [`${indent}{}`];
|
|
108
|
+
const lines = [`${indent}{`];
|
|
109
|
+
entries.forEach(([key, nested], index) => {
|
|
110
|
+
const nestedLines = formatActivityJson(nested, depth + 1);
|
|
111
|
+
const nestedIndent = " ".repeat(depth + 1);
|
|
112
|
+
lines.push(
|
|
113
|
+
`${nestedIndent}${JSON.stringify(key)}: ${nestedLines[0]!.slice(nestedIndent.length)}`,
|
|
114
|
+
...nestedLines.slice(1),
|
|
115
|
+
);
|
|
116
|
+
if (index < entries.length - 1) lines[lines.length - 1] += ",";
|
|
117
|
+
});
|
|
118
|
+
lines.push(`${indent}}`);
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
121
|
+
return [`${indent}${JSON.stringify(value)}`];
|
|
87
122
|
}
|
|
88
123
|
|
|
89
124
|
function serializeActivityValue(value: unknown): string {
|
|
90
125
|
const seen = new WeakSet<object>();
|
|
91
126
|
let text: string;
|
|
92
127
|
try {
|
|
93
|
-
|
|
128
|
+
const normalized =
|
|
94
129
|
JSON.stringify(
|
|
95
130
|
value,
|
|
96
131
|
(_key, nested) => {
|
|
@@ -101,8 +136,8 @@ function serializeActivityValue(value: unknown): string {
|
|
|
101
136
|
}
|
|
102
137
|
return nested;
|
|
103
138
|
},
|
|
104
|
-
2,
|
|
105
139
|
) ?? JSON.stringify(String(value));
|
|
140
|
+
text = formatActivityJson(JSON.parse(normalized)).join("\n");
|
|
106
141
|
} catch {
|
|
107
142
|
text = JSON.stringify(String(value));
|
|
108
143
|
}
|
|
@@ -113,17 +148,17 @@ function serializeActivityValue(value: unknown): string {
|
|
|
113
148
|
}
|
|
114
149
|
|
|
115
150
|
function renderToolActivityHtml(tool: ToolActivity): string {
|
|
116
|
-
const evidence = [`arguments: ${tool.args}`];
|
|
151
|
+
const evidence = [`"arguments": ${tool.args}`];
|
|
117
152
|
if (tool.droppedUpdates > 0) {
|
|
118
153
|
evidence.push(`… [${tool.droppedUpdates} earlier updates omitted]`);
|
|
119
154
|
}
|
|
120
155
|
tool.updates.forEach((update, index) => {
|
|
121
156
|
evidence.push(
|
|
122
|
-
`update ${tool.droppedUpdates + index + 1}: ${update}`,
|
|
157
|
+
`"update ${tool.droppedUpdates + index + 1}": ${update}`,
|
|
123
158
|
);
|
|
124
159
|
});
|
|
125
160
|
if (tool.complete && tool.result !== undefined) {
|
|
126
|
-
evidence.push(
|
|
161
|
+
evidence.push(`"${tool.isError ? "error" : "result"}": ${tool.result}`);
|
|
127
162
|
}
|
|
128
163
|
const status = tool.complete
|
|
129
164
|
? tool.isError
|
|
@@ -146,13 +181,14 @@ function toolMessageSize(tools: readonly ToolActivity[]): number {
|
|
|
146
181
|
return renderTelegramToolActivityHtml(tools).length;
|
|
147
182
|
}
|
|
148
183
|
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
184
|
+
export function renderTelegramThinkingActivityHtml(
|
|
185
|
+
text: string,
|
|
186
|
+
complete: boolean,
|
|
187
|
+
): string {
|
|
188
|
+
return [
|
|
189
|
+
`<b>${TELEGRAM_THINKING_ACTIVITY_ICON}  thinking:</b> <code>${complete ? "done" : "running"}</code>`,
|
|
190
|
+
`<blockquote expandable>${escapeHtml(text)}</blockquote>`,
|
|
191
|
+
].join("\n");
|
|
156
192
|
}
|
|
157
193
|
|
|
158
194
|
export interface TelegramActivityVerbosityRuntime {
|
|
@@ -163,19 +199,16 @@ export interface TelegramActivityVerbosityRuntime {
|
|
|
163
199
|
}
|
|
164
200
|
|
|
165
201
|
export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
166
|
-
|
|
202
|
+
getActivityMode: () => "quiet" | "thinking" | "tools" | "verbose";
|
|
167
203
|
resolveTarget: (event: TelegramActivityEvent) => TelegramTarget | undefined;
|
|
168
204
|
captureAuthority: () => TAuthority;
|
|
169
205
|
isAuthorityActive: (authority: TAuthority) => boolean;
|
|
170
206
|
sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
|
|
171
|
-
sendRichMessageDraft: (
|
|
172
|
-
body: TelegramSendRichMessageDraftBody,
|
|
173
|
-
) => Promise<boolean>;
|
|
174
207
|
editMessageText: (
|
|
175
208
|
body: TelegramEditMessageTextBody,
|
|
176
209
|
) => Promise<"edited" | "unchanged">;
|
|
177
210
|
recordFailure?: (
|
|
178
|
-
operation: "reasoning-
|
|
211
|
+
operation: "reasoning-send" | "reasoning-edit" | "tool-send" | "tool-edit",
|
|
179
212
|
event: TelegramActivityEvent,
|
|
180
213
|
error: unknown,
|
|
181
214
|
) => void;
|
|
@@ -188,8 +221,10 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
188
221
|
let target: TelegramTarget | undefined;
|
|
189
222
|
let reasoningBuffer = "";
|
|
190
223
|
let reasoningChars = 0;
|
|
191
|
-
let
|
|
192
|
-
let
|
|
224
|
+
let reasoningMessageFrames = 0;
|
|
225
|
+
let lastReasoningMessageChars = 0;
|
|
226
|
+
let reasoningMessage: ReasoningMessage | undefined;
|
|
227
|
+
let reasoningBlocked = false;
|
|
193
228
|
let toolMessage: ToolMessage | undefined;
|
|
194
229
|
const tools = new Map<string, ToolActivity>();
|
|
195
230
|
const toolOrder: string[] = [];
|
|
@@ -200,8 +235,10 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
200
235
|
target = undefined;
|
|
201
236
|
reasoningBuffer = "";
|
|
202
237
|
reasoningChars = 0;
|
|
203
|
-
|
|
204
|
-
|
|
238
|
+
reasoningMessageFrames = 0;
|
|
239
|
+
lastReasoningMessageChars = 0;
|
|
240
|
+
reasoningMessage = undefined;
|
|
241
|
+
reasoningBlocked = false;
|
|
205
242
|
toolMessage = undefined;
|
|
206
243
|
tools.clear();
|
|
207
244
|
toolOrder.length = 0;
|
|
@@ -209,7 +246,7 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
209
246
|
const hasAuthority = (): boolean =>
|
|
210
247
|
authority !== undefined && deps.isAuthorityActive(authority);
|
|
211
248
|
const ensureActivity = (event: TelegramActivityEvent): boolean => {
|
|
212
|
-
if (
|
|
249
|
+
if (deps.getActivityMode() === "quiet") return false;
|
|
213
250
|
if (activityId === event.activityId) return hasAuthority();
|
|
214
251
|
clearActivity();
|
|
215
252
|
const resolvedTarget = deps.resolveTarget(event);
|
|
@@ -222,44 +259,68 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
222
259
|
const closeToolBatch = () => {
|
|
223
260
|
toolMessage = undefined;
|
|
224
261
|
};
|
|
225
|
-
const
|
|
262
|
+
const publishReasoning = async (
|
|
226
263
|
event: TelegramActivityEvent,
|
|
227
264
|
acceptedGeneration: number,
|
|
265
|
+
complete: boolean,
|
|
228
266
|
) => {
|
|
229
267
|
if (
|
|
230
268
|
generation !== acceptedGeneration ||
|
|
231
269
|
!target ||
|
|
232
|
-
!hasAuthority()
|
|
270
|
+
!hasAuthority() ||
|
|
271
|
+
reasoningBlocked
|
|
233
272
|
) {
|
|
234
273
|
return;
|
|
235
274
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
275
|
+
let retained = reasoningBuffer;
|
|
276
|
+
let body = "";
|
|
277
|
+
do {
|
|
278
|
+
const omitted = reasoningChars - retained.length;
|
|
279
|
+
const text = redactActivityText(
|
|
280
|
+
omitted > 0
|
|
281
|
+
? `… [${omitted} earlier chars omitted]\n${retained}`
|
|
282
|
+
: retained,
|
|
283
|
+
);
|
|
284
|
+
body = renderTelegramThinkingActivityHtml(text, complete);
|
|
285
|
+
if (body.length <= TELEGRAM_ACTIVITY_MESSAGE_MAX_CHARS) break;
|
|
286
|
+
retained = retained.slice(-Math.max(1, Math.floor(retained.length * 0.75)));
|
|
287
|
+
} while (retained.length > 1);
|
|
288
|
+
const canEdit =
|
|
289
|
+
reasoningMessage && targetEquals(reasoningMessage.target, target);
|
|
241
290
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
291
|
+
if (canEdit && reasoningMessage) {
|
|
292
|
+
await deps.editMessageText({
|
|
293
|
+
chat_id: target.chatId,
|
|
294
|
+
message_id: reasoningMessage.messageId,
|
|
295
|
+
text: body,
|
|
296
|
+
parse_mode: "HTML",
|
|
297
|
+
link_preview_options: { is_disabled: true },
|
|
298
|
+
});
|
|
299
|
+
} else {
|
|
300
|
+
const sent = await deps.sendMessage({
|
|
301
|
+
chat_id: target.chatId,
|
|
302
|
+
...(target.threadId === undefined
|
|
303
|
+
? {}
|
|
304
|
+
: { message_thread_id: target.threadId }),
|
|
305
|
+
text: body,
|
|
306
|
+
parse_mode: "HTML",
|
|
307
|
+
link_preview_options: { is_disabled: true },
|
|
308
|
+
});
|
|
309
|
+
reasoningMessage = {
|
|
310
|
+
messageId: sent.message_id,
|
|
311
|
+
target: { ...target },
|
|
312
|
+
};
|
|
313
|
+
}
|
|
258
314
|
if (generation !== acceptedGeneration) return;
|
|
259
|
-
|
|
260
|
-
|
|
315
|
+
reasoningMessageFrames += 1;
|
|
316
|
+
lastReasoningMessageChars = reasoningChars;
|
|
261
317
|
} catch (error) {
|
|
262
|
-
deps.recordFailure?.(
|
|
318
|
+
deps.recordFailure?.(
|
|
319
|
+
canEdit ? "reasoning-edit" : "reasoning-send",
|
|
320
|
+
event,
|
|
321
|
+
error,
|
|
322
|
+
);
|
|
323
|
+
reasoningBlocked = true;
|
|
263
324
|
}
|
|
264
325
|
};
|
|
265
326
|
const publishTool = async (
|
|
@@ -288,6 +349,7 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
288
349
|
message_id: toolMessage.messageId,
|
|
289
350
|
text: renderTelegramToolActivityHtml(nextTools),
|
|
290
351
|
parse_mode: "HTML",
|
|
352
|
+
link_preview_options: { is_disabled: true },
|
|
291
353
|
});
|
|
292
354
|
if (generation !== acceptedGeneration) return;
|
|
293
355
|
toolMessage.tools = nextTools;
|
|
@@ -300,6 +362,7 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
300
362
|
: { message_thread_id: target.threadId }),
|
|
301
363
|
text: renderTelegramToolActivityHtml([tool]),
|
|
302
364
|
parse_mode: "HTML",
|
|
365
|
+
link_preview_options: { is_disabled: true },
|
|
303
366
|
});
|
|
304
367
|
if (generation !== acceptedGeneration) return;
|
|
305
368
|
toolMessage = {
|
|
@@ -317,9 +380,17 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
317
380
|
acceptedGeneration: number,
|
|
318
381
|
) => {
|
|
319
382
|
if (!ensureActivity(event)) {
|
|
320
|
-
if (
|
|
383
|
+
if (
|
|
384
|
+
activityId === event.activityId &&
|
|
385
|
+
deps.getActivityMode() === "quiet"
|
|
386
|
+
) {
|
|
387
|
+
clearActivity();
|
|
388
|
+
}
|
|
321
389
|
return;
|
|
322
390
|
}
|
|
391
|
+
const mode = deps.getActivityMode();
|
|
392
|
+
const showThinking = mode === "thinking" || mode === "verbose";
|
|
393
|
+
const showTools = mode === "tools" || mode === "verbose";
|
|
323
394
|
if (
|
|
324
395
|
event.type === "assistant-text-delta" ||
|
|
325
396
|
event.type === "assistant-segment" ||
|
|
@@ -329,31 +400,41 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
329
400
|
closeToolBatch();
|
|
330
401
|
}
|
|
331
402
|
if (event.type === "reasoning-delta") {
|
|
403
|
+
if (!showThinking) return;
|
|
332
404
|
reasoningChars += event.delta.length;
|
|
333
405
|
reasoningBuffer = `${reasoningBuffer}${event.delta}`.slice(
|
|
334
406
|
-TELEGRAM_REASONING_BUFFER_MAX_CHARS,
|
|
335
407
|
);
|
|
336
408
|
if (
|
|
337
|
-
|
|
338
|
-
(
|
|
339
|
-
reasoningChars -
|
|
409
|
+
reasoningMessageFrames < TELEGRAM_REASONING_MESSAGE_MAX_FRAMES &&
|
|
410
|
+
(reasoningMessageFrames === 0 ||
|
|
411
|
+
reasoningChars - lastReasoningMessageChars >= 160)
|
|
340
412
|
) {
|
|
341
|
-
await
|
|
413
|
+
await publishReasoning(event, acceptedGeneration, false);
|
|
342
414
|
}
|
|
343
415
|
return;
|
|
344
416
|
}
|
|
345
417
|
if (event.type === "reasoning-end") {
|
|
346
|
-
if (
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
418
|
+
if (!showThinking) return;
|
|
419
|
+
if (reasoningChars === 0 && event.text) {
|
|
420
|
+
reasoningChars = event.text.length;
|
|
421
|
+
reasoningBuffer = event.text.slice(
|
|
422
|
+
-TELEGRAM_REASONING_BUFFER_MAX_CHARS,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
if (reasoningChars > 0 && !reasoningBlocked) {
|
|
426
|
+
await publishReasoning(event, acceptedGeneration, true);
|
|
351
427
|
}
|
|
352
428
|
reasoningBuffer = "";
|
|
353
429
|
reasoningChars = 0;
|
|
430
|
+
reasoningMessageFrames = 0;
|
|
431
|
+
lastReasoningMessageChars = 0;
|
|
432
|
+
reasoningMessage = undefined;
|
|
433
|
+
reasoningBlocked = false;
|
|
354
434
|
return;
|
|
355
435
|
}
|
|
356
436
|
if (event.type === "tool-start") {
|
|
437
|
+
if (!showTools) return;
|
|
357
438
|
tools.set(event.toolCallId, {
|
|
358
439
|
id: event.toolCallId,
|
|
359
440
|
name: event.toolName,
|
|
@@ -366,6 +447,7 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
366
447
|
return;
|
|
367
448
|
}
|
|
368
449
|
if (event.type === "tool-update") {
|
|
450
|
+
if (!showTools) return;
|
|
369
451
|
const tool = tools.get(event.toolCallId);
|
|
370
452
|
if (!tool) return;
|
|
371
453
|
tool.updates.push(serializeActivityValue(event.update));
|
|
@@ -376,6 +458,7 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
376
458
|
return;
|
|
377
459
|
}
|
|
378
460
|
if (event.type === "tool-end") {
|
|
461
|
+
if (!showTools) return;
|
|
379
462
|
const tool = tools.get(event.toolCallId) ?? {
|
|
380
463
|
id: event.toolCallId,
|
|
381
464
|
name: event.toolName,
|
|
@@ -400,6 +483,9 @@ export function createTelegramActivityVerbosityRuntime<TAuthority>(deps: {
|
|
|
400
483
|
return;
|
|
401
484
|
}
|
|
402
485
|
if (event.type === "agent-end" || event.type === "agent-settled") {
|
|
486
|
+
if (reasoningMessage && reasoningChars > 0 && !reasoningBlocked) {
|
|
487
|
+
await publishReasoning(event, acceptedGeneration, true);
|
|
488
|
+
}
|
|
403
489
|
clearActivity();
|
|
404
490
|
}
|
|
405
491
|
};
|
package/lib/config.ts
CHANGED
|
@@ -44,7 +44,6 @@ export interface TelegramOutboundHandlerConfig extends CommandTemplateObjectConf
|
|
|
44
44
|
export type TelegramTimeMode = "hidden" | "always" | "interval";
|
|
45
45
|
|
|
46
46
|
export interface TelegramTimeConfig {
|
|
47
|
-
injectionMode?: TelegramTimeMode;
|
|
48
47
|
interval?: number;
|
|
49
48
|
}
|
|
50
49
|
|
|
@@ -55,7 +54,11 @@ export interface ResolvedTelegramTimeConfig {
|
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
export type TelegramAssistantRenderingMode = "rich" | "html";
|
|
58
|
-
export type TelegramActivityVerbosity =
|
|
57
|
+
export type TelegramActivityVerbosity =
|
|
58
|
+
| "quiet"
|
|
59
|
+
| "thinking"
|
|
60
|
+
| "tools"
|
|
61
|
+
| "verbose";
|
|
59
62
|
|
|
60
63
|
export interface TelegramConfig {
|
|
61
64
|
/** @deprecated persisted identity belongs in profiles.default; retained for effective/legacy views */
|
|
@@ -76,6 +79,7 @@ export interface TelegramConfig {
|
|
|
76
79
|
rendering?: TelegramAssistantRenderingMode;
|
|
77
80
|
proactivePush?: boolean;
|
|
78
81
|
activity?: TelegramActivityVerbosity;
|
|
82
|
+
timeInjection?: TelegramTimeMode;
|
|
79
83
|
/** @deprecated use activity */
|
|
80
84
|
activityVerbosity?: TelegramActivityVerbosity;
|
|
81
85
|
};
|
|
@@ -756,7 +760,14 @@ export function createTelegramActivityVerbosityGetter(
|
|
|
756
760
|
return () => {
|
|
757
761
|
const assistant = configStore.get().assistant;
|
|
758
762
|
if (assistant?.activity !== undefined) {
|
|
759
|
-
|
|
763
|
+
if (
|
|
764
|
+
assistant.activity === "thinking" ||
|
|
765
|
+
assistant.activity === "tools" ||
|
|
766
|
+
assistant.activity === "verbose"
|
|
767
|
+
) {
|
|
768
|
+
return assistant.activity;
|
|
769
|
+
}
|
|
770
|
+
return "quiet";
|
|
760
771
|
}
|
|
761
772
|
return assistant?.activityVerbosity === "verbose" ? "verbose" : "quiet";
|
|
762
773
|
};
|
|
@@ -834,10 +845,11 @@ function getSystemTimezone(): string {
|
|
|
834
845
|
|
|
835
846
|
export function resolveTelegramTimeConfig(
|
|
836
847
|
raw: TelegramTimeConfig | undefined,
|
|
848
|
+
timeInjection: TelegramTimeMode | undefined = undefined,
|
|
837
849
|
): ResolvedTelegramTimeConfig {
|
|
838
850
|
const injectionMode: TelegramTimeMode =
|
|
839
|
-
|
|
840
|
-
?
|
|
851
|
+
timeInjection === "always" || timeInjection === "interval"
|
|
852
|
+
? timeInjection
|
|
841
853
|
: "hidden";
|
|
842
854
|
const interval =
|
|
843
855
|
typeof raw?.interval === "number" && raw.interval > 0
|
|
@@ -850,13 +862,25 @@ export function resolveTelegramTimeConfig(
|
|
|
850
862
|
export function createTelegramTimeConfigGetter(
|
|
851
863
|
configStore: Pick<TelegramConfigStore, "get">,
|
|
852
864
|
): () => ResolvedTelegramTimeConfig {
|
|
853
|
-
return () =>
|
|
865
|
+
return () => {
|
|
866
|
+
const config = configStore.get();
|
|
867
|
+
return resolveTelegramTimeConfig(
|
|
868
|
+
config.time,
|
|
869
|
+
config.assistant?.timeInjection,
|
|
870
|
+
);
|
|
871
|
+
};
|
|
854
872
|
}
|
|
855
873
|
|
|
856
874
|
export function createTelegramTimeInjectionModeGetter(
|
|
857
875
|
configStore: Pick<TelegramConfigStore, "get">,
|
|
858
876
|
): () => TelegramTimeMode {
|
|
859
|
-
return () =>
|
|
877
|
+
return () => {
|
|
878
|
+
const config = configStore.get();
|
|
879
|
+
return resolveTelegramTimeConfig(
|
|
880
|
+
config.time,
|
|
881
|
+
config.assistant?.timeInjection,
|
|
882
|
+
).injectionMode;
|
|
883
|
+
};
|
|
860
884
|
}
|
|
861
885
|
|
|
862
886
|
export function createTelegramTimeInjectionModeSetter(
|
|
@@ -865,19 +889,12 @@ export function createTelegramTimeInjectionModeSetter(
|
|
|
865
889
|
return async (injectionMode) => {
|
|
866
890
|
await loadLatestTelegramConfig(configStore);
|
|
867
891
|
const current = configStore.get();
|
|
868
|
-
if (injectionMode === "hidden") {
|
|
869
|
-
const { injectionMode: _injectionMode, ...remainingTime } =
|
|
870
|
-
current.time ?? {};
|
|
871
|
-
const next = { ...current };
|
|
872
|
-
if (Object.keys(remainingTime).length > 0) next.time = remainingTime;
|
|
873
|
-
else delete next.time;
|
|
874
|
-
configStore.set(next);
|
|
875
|
-
await configStore.persist(next);
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
892
|
const next = {
|
|
879
893
|
...current,
|
|
880
|
-
|
|
894
|
+
assistant: {
|
|
895
|
+
...(current.assistant ?? {}),
|
|
896
|
+
timeInjection: injectionMode,
|
|
897
|
+
},
|
|
881
898
|
};
|
|
882
899
|
configStore.set(next);
|
|
883
900
|
await configStore.persist(next);
|
package/lib/menu-settings.ts
CHANGED
|
@@ -228,8 +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> (default): show
|
|
232
|
-
"<code>-</code> <code>
|
|
231
|
+
"<code>-</code> <code>quiet</code> (default): show no thinking or tool traffic.",
|
|
232
|
+
"<code>-</code> <code>thinking</code>: show persistent collapsed thinking.",
|
|
233
|
+
"<code>-</code> <code>tools</code>: show persistent collapsed tool details.",
|
|
234
|
+
"<code>-</code> <code>verbose</code>: show both thinking and tools.",
|
|
233
235
|
].join("\n");
|
|
234
236
|
}
|
|
235
237
|
|
|
@@ -459,7 +461,12 @@ export function buildAssistantRenderingSettingsReplyMarkup(
|
|
|
459
461
|
export function buildActivityVerbositySettingsReplyMarkup(
|
|
460
462
|
verbosity: TelegramActivityVerbosity,
|
|
461
463
|
): TelegramSettingsMenuReplyMarkup {
|
|
462
|
-
const values: TelegramActivityVerbosity[] = [
|
|
464
|
+
const values: TelegramActivityVerbosity[] = [
|
|
465
|
+
"quiet",
|
|
466
|
+
"thinking",
|
|
467
|
+
"tools",
|
|
468
|
+
"verbose",
|
|
469
|
+
];
|
|
463
470
|
return {
|
|
464
471
|
inline_keyboard: [
|
|
465
472
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
@@ -712,7 +719,12 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
712
719
|
}
|
|
713
720
|
if (data.startsWith("settings:set:activity-verbosity:")) {
|
|
714
721
|
const verbosity = data.slice("settings:set:activity-verbosity:".length);
|
|
715
|
-
if (
|
|
722
|
+
if (
|
|
723
|
+
verbosity === "quiet" ||
|
|
724
|
+
verbosity === "thinking" ||
|
|
725
|
+
verbosity === "tools" ||
|
|
726
|
+
verbosity === "verbose"
|
|
727
|
+
) {
|
|
716
728
|
await deps.setActivityVerbosity(verbosity);
|
|
717
729
|
await updateActivityVerbositySettingsMessage(deps);
|
|
718
730
|
await deps.answerCallbackQuery(
|
package/lib/telegram-api.ts
CHANGED
|
@@ -204,10 +204,15 @@ export interface TelegramReplyParameters {
|
|
|
204
204
|
message_thread_id?: number;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
export interface TelegramLinkPreviewOptions {
|
|
208
|
+
is_disabled?: boolean;
|
|
209
|
+
}
|
|
210
|
+
|
|
207
211
|
export type TelegramSendMessageBody = Record<string, unknown> & {
|
|
208
212
|
chat_id: number;
|
|
209
213
|
text: string;
|
|
210
214
|
parse_mode?: "HTML";
|
|
215
|
+
link_preview_options?: TelegramLinkPreviewOptions;
|
|
211
216
|
reply_markup?: unknown;
|
|
212
217
|
reply_parameters?: TelegramReplyParameters;
|
|
213
218
|
};
|
|
@@ -327,6 +332,7 @@ export type TelegramEditMessageTextBody = Record<string, unknown> & {
|
|
|
327
332
|
text?: string;
|
|
328
333
|
rich_message?: TelegramInputRichMessage;
|
|
329
334
|
parse_mode?: "HTML";
|
|
335
|
+
link_preview_options?: TelegramLinkPreviewOptions;
|
|
330
336
|
reply_markup?: unknown;
|
|
331
337
|
};
|
|
332
338
|
|