@llblab/pi-telegram 0.20.5 → 0.21.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 CHANGED
@@ -83,7 +83,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
83
83
  - 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.
84
84
  - Telegram private-chat Threaded Mode treats `All` as an aggregate/control surface, not a durable `General` thread and not a process launcher. Leader activation creates/reuses a leader thread in the private bot DM (chat = allowedUserId) proactively. If all threads are closed and the owner writes from `All`, a first new unbound thread should be reclaimed by the leader when the leader lacks an active bound thread. Later unknown threads require explicit routing or restore; creating another Pi instance is a manual terminal action followed by `/telegram-connect`.
85
85
  - Unbound thread detection: when a message arrives from the owner in an unknown `message_thread_id` while Threaded Mode is active, the default runtime first reclaims it for the leader if the leader has no active bound thread and routes the prompt locally. Later unknown threads are preserved by default and receive an in-thread reroute chooser that can send the captured prompt to a live thread or restore a stale leader/follower binding through explicit restore actions; destructive cleanup happens only after explicit user confirmation and through `thread-reconciler`. If the operator wants another Pi instance, they start Pi in a terminal and run `/telegram-connect`; Telegram-originated hidden auto-spawn and `/thread`-style process launch flows are intentionally absent. If Threaded Mode is unavailable, the message is processed normally through classic routing.
86
- - 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.
86
+ - 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. Public activity handlers run through isolated asynchronous per-handler queues; lifecycle hooks enqueue normalized events and never await consumer work.
87
87
  - 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.
88
88
  - 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 threads (owner writing in `All`) are preserved until the user explicitly chooses a reroute/restore/new-instance action that makes cleanup eligible; any destructive cleanup must go through `thread-reconciler`. Unknown `forum_topic_created` service events are observations, not destructive cleanup proof.
89
89
  - 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.
@@ -159,9 +159,9 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
159
159
 
160
160
  - Scheduling and lifecycle: `queue`, `runtime`, `lifecycle`, `locks`
161
161
  - Telegram transport, polling, bus, sync, and inbound flow: `api`, `polling`, `bus`, `bus-api`, `bus-leader`, `bus-follower`, `ownership`, `target`, `sync`, `thread-reconciler`, `threads`, `updates`, `routing`, `media`, `turns`, `inbound`, `config`, `setup`
162
- - Response surfaces: `preview`, `replies`, `rendering`, `keyboard`, `outbound-markup`, `outbound-attachments`, `outbound`, `voice`, `status`
162
+ - Response surfaces: `preview`, `replies`, `rendering`, `keyboard`, `delivery`, `activity`, `outbound-markup`, `outbound-attachments`, `outbound`, `voice`, `status`
163
163
  - Controls and application menu UI: `commands`, `menu`, `menu-model`, `menu-thinking`, `menu-status`, `menu-queue`, `model`, `prompts`
164
- - Extension platform: `sections` owns section registry, token mapping, callback dispatch, context building, and its globalThis bridge; `voice` owns the voice-provider registry and its globalThis bridge
164
+ - Extension platform: `sections` owns section registry, token mapping, callback dispatch, context building, and its globalThis bridge; `delivery` owns target-aware extension views, logical handles, target policy, ordering, lifecycle fencing, and its globalThis runtime membrane; `activity` owns normalized lifecycle registration, activity/source identity, non-blocking dispatch, delivery contexts, and its globalThis runtime membrane; `voice` owns the voice-provider registry and its globalThis bridge
165
165
  - Pi SDK boundary: `pi` owns direct pi imports and bound extension API ports; `bindings` owns pi-facing command/tool/lifecycle registration wiring extracted from the entrypoint
166
166
 
167
167
  ## 6.5 Entrypoint And Import Boundaries
@@ -203,9 +203,11 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
203
203
  - Inline UI labels and dialogs follow [`docs/ui-style.md`](./docs/ui-style.md): action buttons use emoji plus capitalized action text, state & navigation buttons show state and lead to a submenu, first-level submenus start with `⬆️ Main menu` while deeper submenus start with `⬆️ Back`, boolean toggles use Capitalized horizontal `On`/`Off` with green active `On`, yellow active `Off`, and black inactive dots, tabs use capitalized labels with purple default-state and yellow elevated-state active dots and black inactive dots, vertical option lists mark only the current value green, and confirmation dialogs use a single bold text-only question with emoji on buttons only.
204
204
  - Inbound text/media may be transformed through configured `inboundHandlers` before queueing; legacy `attachmentHandlers` are deprecated compatibility aliases appended after `inboundHandlers`; active-turn outbound files must flow through `telegram_attach`, while explicit local/TUI Telegram sends use `telegram_attach` for files or `telegram_message` for text/buttons
205
205
  - Long Telegram text split recovery belongs to `text-groups`: keep it conservative, short-debounced, same chat/user/message-id contiguous, and gated by near-limit human text so normal rapid follow-ups and slash commands stay separate
206
- - Public API boundaries live in [`docs/public-api.md`](./docs/public-api.md): companion extensions must use public API domain subpaths such as `@llblab/pi-telegram/sections`, `/voice`, `/inbound`, `/outbound`, and `/updates`; `0.12.0` intentionally removes the published `@llblab/pi-telegram/lib/*.ts` compatibility wildcard
207
- - Public handler API matrix: low-level buses use `registerTelegramUpdateHandler(handler)`, `registerTelegramInboundHandler(kind, handler)`, and `registerTelegramOutboundHandler(kind, handler)` without ids; high-level surfaces use stable identity (`registerTelegramSection({ id, ... })`, `registerTelegramVoiceTranscriptionProvider(provider, { id })`, and `registerTelegramVoiceSynthesisProvider(provider, { id })`). Inbound handlers and command-backed outbound handlers use command templates as the standard config contract; built-in outbound buttons use inline keyboards plus callback routing because no polling command execution is needed
206
+ - Public API boundaries live in [`docs/public-api.md`](./docs/public-api.md): companion extensions must use public API domain subpaths such as `@llblab/pi-telegram/sections`, `/delivery`, `/voice`, `/inbound`, `/outbound`, and `/updates`; `0.12.0` intentionally removes the published `@llblab/pi-telegram/lib/*.ts` compatibility wildcard
207
+ - `Companion` names the extension consumer/product relationship, not a core domain responsibility. Source headers, Zones, module names, public API names, and domain docs should use the owned capability (`delivery`, `activity`, `sections`, `voice`, or extension interop); reserve companion terminology for the external extension that composes those APIs.
208
+ - Public handler API matrix: low-level buses use `registerTelegramUpdateHandler(handler)`, `registerTelegramInboundHandler(kind, handler)`, and `registerTelegramOutboundHandler(kind, handler)` without ids; high-level surfaces use stable identity (`registerTelegramSection({ id, ... })`, `registerTelegramVoiceTranscriptionProvider(provider, { id })`, and `registerTelegramVoiceSynthesisProvider(provider, { id })`). Imperative `/delivery` operations resolve the current runtime on every call and use generation-bound logical handles rather than registration ids or captured Pi contexts. Inbound handlers and command-backed outbound handlers use command templates as the standard config contract; built-in outbound buttons use inline keyboards plus callback routing because no polling command execution is needed
208
209
  - Telegram prompt-template commands are discovered from Pi slash commands with `source: "prompt"`; Pi template names are mapped to Bot API-compatible aliases (`fix-tests` → `/fix_tests`), aliases that conflict with built-in bridge commands or hidden shortcuts are not displayed, prompt-template aliases stay out of the Telegram bot command menu, and the bridge expands template files before queueing because extension-originated `sendUserMessage()` bypasses Pi's interactive template expansion
210
+ - Target-aware companion delivery follows [`docs/delivery.md`](./docs/delivery.md): `active-turn`, `instance`, `aggregate`, and explicit target scopes resolve through current profile/role state; followers may target only their assigned thread or aggregate surface, leaders may target current live bindings, logical handles reconcile chunked send/edit/delete, and reload/session replacement invalidates old generations
209
211
  - Unknown callback data not owned by pi-telegram prefixes (`compact:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `section:`, `settings:`) may be forwarded as `[callback] <data>` after built-in handlers decline it; update-handler extensions should follow `docs/callback-namespaces.md` and must not poll the same bot independently
210
212
  - Command templates stay compact and shell-free: no `command` field, no shell execution, inline defaults are allowed as `{name=default}`, `template` may be a string or an ordered composition array, only `args`/`defaults` inherit into leaves, top-level `timeout` wraps composed sequences, stdout pipes to the next step's stdin by default, and multi-step work should use `template: [...]` rather than provider-specific fields; `pipe` is only a legacy local alias
211
213
  - Command-template documentation examples should use portable executable placeholders such as `/path/to/stt` and `/path/to/tts`, not host-local skill paths or machine-specific install locations
package/BACKLOG.md CHANGED
@@ -1,70 +1,6 @@
1
1
  # Project Backlog
2
2
 
3
- _This backlog tracks only open release-relevant work: live promoted-follower verification, evidence-gated Telegram client/runtime follow-ups, and upstream Pi API blockers. Completed validation evidence belongs in `CHANGELOG.md`, not in this queue._
4
-
5
- ## P0 — Private Guest DM Peer Attribution
6
-
7
- Evidence: live private Guest Mode produced `[telegram|guest:<owner>]` for an owner-authored DM turn even though `guest` must identify the remote conversation peer. Code inspection confirms that private guest routing falls back to `fromPeer` whenever an owner-authored message has no usable `reply_to_message`; because `from.id` then equals the configured `allowedUserId`, the owner is mislabeled as the guest. Existing coverage protects incoming guest messages and owner replies with explicit replied-guest metadata, but does not cover owner-authored private guest messages without reply context.
8
-
9
- Planned work:
10
-
11
- - [ ] Capture or minimize the raw private `guest_message` shape for owner-authored turns without reply context and identify the stable remote-peer fields supplied by Telegram (`chat` identity, username/name/id, or another explicit peer field) before choosing a resolver.
12
- - [ ] Centralize Guest Mode peer attribution: group turns use the group title; private non-owner turns use the sender; private owner turns use the replied guest when present and otherwise the remote private-chat peer.
13
- - [ ] Compare ownership by Telegram user id (`allowedUserId`), not display name or username. Never emit the configured owner as `guest`; if Telegram omits a username, fall back to the remote peer's stable name/id rather than the owner.
14
- - [ ] Keep `[reply|from:...]` and `[attachments|from:...]` source attribution aligned with the same resolved peer without changing the current-turn/source-context distinction.
15
- - [ ] Add regressions for incoming private guests, owner replies, owner-authored no-reply turns, missing usernames, username changes, and named-profile pairing identities.
16
- - [ ] Update the prompt-context contract/docs only after the minimized Telegram fixture establishes the actual private Guest Mode field semantics.
17
-
18
- Done when: `[telegram|guest:...]` always identifies the remote peer or group for private/group Guest Mode, never the paired owner, and reply/attachment provenance remains source-correct.
19
-
20
- ## P0 — Guest Reply File And Audio Delivery
21
-
22
- Evidence: live Guest Mode accepted `telegram_attach` during an active guest turn and reported the file as queued, but delivered nothing. Code inspection confirms that guest turns use sentinel `chatId: 0`; the tool appends files to `queuedAttachments`, then the agent-end guest branch sends only `answerGuestQuery` text and returns before queued attachments or voice artifacts run. Telegram's `answerGuestQuery` accepts one `InlineQueryResult`, not ordinary `sendDocument`/`sendVoice` multipart delivery, so local artifacts require a guest-specific result plan rather than reuse of chat/thread attachment transport.
23
-
24
- Planned work:
25
-
26
- - [x] Fail closed immediately for unsupported guest attachments until guest delivery is available; never return `Queued` when the guest agent-end path cannot consume the artifact.
27
- - [x] Map the current Bot API `InlineQueryResult` capabilities for document, photo, audio, and voice replies, including URL versus cached `file_id`, caption limits, supported formats, and the one-result-per-guest-query constraint. `answerGuestQuery` accepts exactly one result; local multipart uploads are not accepted there. URL results require public HTTP content (documents only PDF/ZIP, audio MP3, voice OGG/OPUS, photos JPEG up to 5 MB), while cached result variants accept Telegram `document_file_id`, `photo_file_id`, `audio_file_id`, or `voice_file_id`; media captions remain limited to 0–1024 characters after entity parsing.
28
- - [x] Design one guest reply planner that chooses exactly one result: text article, one cached local file/media with answer text reduced to a caption, or one cached synthesized voice/audio result. Guest tool admission rejects a second attachment before mutation. A failure before the one-shot answer may degrade to one text article; an ambiguous/failing `answerGuestQuery` call must not issue a second answer that could duplicate delivery.
29
- - [x] Determine an evidence-backed local-file staging path. Local media must upload through the existing leader-owned multipart transport to the paired owner's bot chat, extract the returned Telegram `file_id`, answer the guest query with the matching cached result, and delete the staging message in `finally`. The staging message can briefly appear or notify the owner; this unavoidable Bot API limitation must be documented, no external hosting is introduced, and cleanup failure must be diagnosed rather than hidden.
30
- - [x] Extend `answerGuestQuery` and bus forwarding from hard-coded article input to the minimal typed result union required by confirmed file/audio/voice cases.
31
- - [x] Route `telegram_attach`, queued outbound artifacts, and `telegram_voice` through the guest planner before the guest branch returns; never call ordinary multipart methods with sentinel `chatId: 0`.
32
- - [x] Preserve follower operation by routing staging and `answerGuestQuery` through the transport leader without duplicate answers or leaked staging messages.
33
- - [x] Add regressions for unsupported fail-closed behavior, document/image/audio/voice result construction, caption fallback, staging cleanup/failure, multiple-file rejection, guest query one-shot semantics, and text fallback after media failure.
34
- - [ ] Capture live private and group Guest Mode evidence for one local document and one synthesized voice/audio reply before claiming support.
35
-
36
- Done when: guest turns never silently lose queued artifacts, one supported local file or audio/voice result can be delivered through `answerGuestQuery` with clear constraints, and unsupported/multi-file cases fail visibly without sending to an unrelated thread.
37
-
38
- ## P1 — Compaction Status Ownership And Native Activity
39
-
40
- Context: Pi already renders its own compaction lifecycle, while pi-telegram currently overrides its terminal status row with `compacting` whenever the shared compaction flag is set. This duplicates Pi-owned state and hides the distinction between Telegram-owned activity and unrelated automatic/session compaction. Manual `/compact` already calls the typing-loop port and automatic compaction starts typing only when an active Telegram turn exists, so the reported absence of Telegram `…typing` needs transport-level and live verification rather than an assumed rewrite.
41
-
42
- Planned work:
43
-
44
- - [x] Remove `compacting` as a pi-telegram terminal status label while retaining the internal compaction flag for queue/dispatch safety and explicit diagnostics.
45
- - [x] Track compaction origin for status projection: confirmed Telegram `/compact` and auto-compaction inside a Telegram-owned turn render normal `Active`; local/autonomous/background compaction keeps the stable `connected`, `leader`, or `follower` role.
46
- - [x] Define and verify the native activity matrix: Telegram-owned compaction targets the invoking/active thread plus `All`; non-Telegram compaction uses the connected instance target plus `All` without changing terminal role semantics.
47
- - [x] Trace manual confirmation, `session_before_compact`, `session_compact`, completion, error, timeout, abort, and shutdown ordering to ensure one keyed typing loop remains active for the whole compaction window and always stops.
48
- - [x] Add transport-level regressions that assert actual `sendChatAction(typing)` targets and keepalive lifecycle, not only invocation of a mocked `startTypingLoop` callback.
49
- - [x] Replace status tests that currently require `compacting` with Telegram-owned `Active` and non-Telegram stable-role cases; preserve `/telegram-status` compaction diagnostics where operationally useful.
50
- - [ ] Capture live evidence for manual Telegram compaction, auto-compaction during a Telegram turn, and non-Telegram auto-compaction before finalizing the activity contract.
51
-
52
- Done when: Pi remains the only terminal owner of the `compacting` label, pi-telegram status reflects Telegram ownership rather than generic compaction, and Telegram native `…typing` remains visible and correctly targeted throughout every confirmed compaction class without leaking afterward.
53
-
54
- ## P1 — Leader Endpoint Loss Recovery
55
-
56
- Context: live evidence showed a process retaining a fresh transport lock and active polling while its Threaded Mode Unix socket path was absent. The likely trigger was external removal of the shared Telegram temp directory while the owner process remained alive. The local server keeps listening on the unlinked Unix socket but `start()` treats its in-memory server handle as sufficient, leader health checks only Bot API transport, and a new instance therefore exhausts follower-registration retries with `ENOENT`. This is a real diagnosable recovery gap, but not yet evidence for a broad readiness protocol or automatic takeover; force-acquiring while the old owner may still run `getUpdates` would risk split-brain.
57
-
58
- Planned work:
59
-
60
- - [x] Reproduce deterministically by unlinking only the active Unix leader socket while its process, polling runtime, and in-memory server remain live. Native Windows named pipes have no equivalent filesystem path to unlink, so recovery remains Unix-specific unless separate named-pipe evidence appears.
61
- - [x] Let the owning Threaded Mode runtime detect an externally missing Unix endpoint during its existing health/prune cadence and restart only the local bus server without changing lock ownership, leader epoch, polling, or thread bindings.
62
- - [x] Make initial follower registration report `live owner / unreachable bus endpoint` after bounded retries, with direct operator guidance; do not add automatic or force takeover without separate evidence that the old owner cannot still poll.
63
- - [x] Keep intentional classic ownership unchanged because classic mode does not require a bus endpoint.
64
- - [x] Add focused regressions for Unix endpoint unlink/rebind, bounded follower diagnosis, leader reload overlap, and no duplicate `getUpdates` ownership; add Windows coverage only for behavior the named-pipe transport can reproduce.
65
- - [ ] Capture live recovery evidence without deleting lock/state or creating a replacement Telegram thread.
66
-
67
- Done when: the confirmed endpoint-loss scenario either self-recovers under the existing owner or produces precise safe remediation, while classic mode and single-owner polling remain unchanged.
3
+ _This backlog tracks only open release-relevant work: 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._
68
4
 
69
5
  ## P1 — Promoted Follower Reload Evidence
70
6
 
@@ -118,6 +54,8 @@ Blocked: upstream Pi core API. Issue: https://github.com/earendil-works/pi/issue
118
54
 
119
55
  Context: Threaded Mode manual followers are separate visible Pi processes. Same-thread `/new` is a different feature: replacing the current Pi session inside the same Telegram thread. Extension-only hacks are rejected because they would desynchronize Pi lifecycle/TUI semantics.
120
56
 
57
+ Current upstream evidence: Pi 0.80.6 safely exposes `ctx.newSession()` to registered extension commands through `ExtensionCommandContext`, including fresh-context rebinding after replacement. Telegram update and callback handlers still receive only `ExtensionContext`, and extension-origin `pi.sendUserMessage()` deliberately disables slash-command handling. The upstream maintainer described an async extension bridge as potentially possible after the current refactor, but no supported API exists yet.
58
+
121
59
  Required upstream shape:
122
60
 
123
61
  - `pi.newSession(...)` or `pi.requestSessionReplacement(...)` callable from trusted extension runtime code.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.21.0: Activity And Delivery Extension Platform
4
+
5
+ - `[Platform Documentation]` Completed 0.21 discoverability across the README, public API inventory/smoke checklist, Activity and Delivery contracts, architecture map, Sections reference, package contents, documented consumer policy examples, and the external demo-project reference. Removed the completed platform epic from `BACKLOG.md` while preserving deferred media/process boundaries and the concrete callback revisit trigger in docs. Impact: extension authors can find and validate the complete supported surface without `/lib` imports, and open-work context now contains only unfinished work.
6
+ - `[Platform Validation]` Added a full-runtime classic-mode regression proving public activity delivery reaches the assigned Telegram surface while `agent_start` remains independent from the consumer's pending Bot API send. Expanded scope coverage to distinguish active-turn, assigned-instance, and aggregate targets and verify autonomous activity can opt into aggregate delivery; existing leader/follower transport, authorization, stale-generation, ordering, chunk reconciliation, and cleanup regressions complete the platform matrix. Impact: the 0.21 extension contracts now carry focused domain, public-boundary, and runtime integration proof.
7
+ - `[Callback Boundary]` Confirmed that the issue #126 consumer shape needs no second managed callback registry: Sections already own interactive Settings namespacing, answers, edits, navigation, and cleanup, while delivered activity rows remain non-interactive and raw update handlers stay the low-level escape hatch. Impact: the platform avoids duplicate callback ownership and records the concrete trigger for revisiting the boundary.
8
+ - `[Activity API]` Added the public `@llblab/pi-telegram/activity` membrane and wired Pi input, agent, assistant text/reasoning, executed-tool, compaction, settlement, and shutdown lifecycle signals into evidence-based activity/source identities. Isolated asynchronous per-handler queues preserve semantic order, coalesce adjacent deltas, fence shutdown generations, report handler failures, and provide fresh target-aware delivery contexts without awaiting consumer work or duplicating bridge previews/finals. Impact: extension consumers can build lifecycle-adjacent Telegram surfaces without private Pi contexts, raw provider messages, or bridge transport ownership.
9
+ - `[Delivery API]` Added the public `@llblab/pi-telegram/delivery` membrane for ownership-gated operational views and chat actions across active-turn, current-instance, aggregate, and explicitly authorized targets. Logical handles reconcile chunked send/edit/delete operations, per-target queues preserve ordering, followers route through the leader transport, and reload/session replacement invalidates old generations without captured Pi contexts. Impact: companion extensions can now deliver lifecycle-adjacent Telegram UI through stable ports instead of importing bridge internals or owning another bot client.
10
+ - `[Public API]` Documented the complete capability inventory, intentional private boundaries, target policy, structured delivery failures, callback boundary decision, and deferred media/process controls; added public-boundary, classic/leader/follower policy, chunk reconciliation, ordering, and lifecycle regressions. Impact: extension authors can distinguish supported platform capabilities from deliberate safety boundaries and the completed 0.21 extension-platform surface.
11
+ - `[Pi Compatibility]` Set the Activity platform floor to Pi/core/AI `0.80.6`, registered the public typed `agent_settled` lifecycle event directly, and aligned Telegram model controls with Pi's `max` thinking level. Impact: activity identity now closes at a supported terminal boundary without unsafe event casts or silently merging unrelated runs on older Pi versions.
12
+ - `[Delivery Lifecycle]` Constructed a fresh Delivery runtime and generation for every session start, shut down displaced bindings, and fenced queued or multi-step in-flight work at every transport boundary. Impact: session replacement cannot revive old handles or let an old operation continue chunks, edits, deletes, or chat actions through a new session.
13
+ - `[Delivery Recovery]` Added recoverable partial handles to failed multi-chunk sends and edit growth, tracking every message id that remains visible after transport failure. Impact: callers can deterministically retry or clean up a partially materialized logical view instead of losing its only valid handle.
14
+ - `[Activity Compaction]` Added explicit abandonment for standalone compaction activities that never receive `session_compact`, triggered by the next run/compaction boundary or the bounded observer timeout, while preserving compaction inside an active agent identity and ignoring late completion. Impact: cancelled or failed compaction cannot leak its activity id or source into an unrelated run.
15
+ - `[Activity Diagnostics]` Reconciled the public contract with implemented diagnostics: handler failures carry redacted identity metadata through the bounded bridge recorder, while 0.21 intentionally exposes no queue phase/length/latency telemetry or drop policy beyond adjacent delta coalescing. Impact: consumers can reason from documented backpressure behavior without relying on diagnostics the runtime does not emit.
16
+ - `[Activity Lifecycle]` Recreated the Activity dispatcher with a fresh generation on every session start while stopping and clearing the retiring generation at shutdown, and bound handler contexts to their exact dispatcher generation and registration. Impact: same-process session replacement resumes normalized lifecycle delivery while blocked handlers from the retiring session fail closed instead of sending stale work through replacement Delivery.
17
+ - `[Guest Identity]` Kept private Guest Mode location identity anchored to the strongest remote-conversation evidence and excluded bot-authored replies from guest resolution, while retaining a non-bot replied peer as a final fallback when chat/caller identity is absent. Impact: replying to a Guest Mode bot response preserves `[telegram|guest:<remote-user>]` alongside `[reply|from:<bot>]` instead of replacing the conversation peer with the bot username.
18
+
19
+ ## 0.20.6: Guest Attribution And Voice Action Hotfix
20
+
21
+ - `[Voice Action Syntax]` Hidden `telegram_voice` actions now accept the intuitive paired form `<!-- telegram_voice ... -->...<!-- /telegram_voice -->` alongside inline, attribute-text, and single-comment multiline forms. The parser captures a non-empty multiline body and preserves language/rate attributes while leaving surrounding prose visible. Impact: agents can use an explicit closing tag without leaking the intended TTS payload as ordinary text.
22
+ - `[Voice Investigation]` Removed the premature Guest Mode generate-before-answer experiment and unrelated voice-pipeline changes after live evidence identified unsupported paired markup as the confounder. After reload, voice-only paired actions delivered exactly one playable voice response with no visible fallback text in both the Aster thread and a correctly attributed private Guest Mode conversation; `telegram_attach` independently delivered the same format. Impact: standard Threaded Mode and private Guest Mode now have direct live proof on the established delivery architecture, while group Guest Mode confirmation remains explicit in `BACKLOG.md`.
23
+ - `[Guest Attribution]` Private Guest Mode now resolves `guest` as the remote conversation peer instead of the paired owner: a non-owner sender identifies itself, while an owner-authored turn prefers the replied peer, then the private-chat identity, then non-owner caller metadata. Username falls back to the remote display name and numeric id, and an unresolved payload records redacted field-presence diagnostics without leaking the owner's identity into `[telegram|guest:...]`. Group Guest Mode continues to use the group title. Impact: the agent sees the third party it is assisting in a direct conversation, not the bot owner who invoked it.
24
+ - `[Validation]` Added route and resolver regressions for owner-authored no-reply DMs, explicit replies, non-owner peers, username changes, missing usernames, numeric fallback, alternate paired profile ids, and owner-only unresolved input. Impact: private attribution and source-context provenance remain distinct while the paired owner can never become the guest label.
25
+ - `[Live Evidence]` After reloading the hotfix workspace, a real owner-authored private Guest Mode invocation arrived as `[telegram|guest:mariannaelunina]` instead of the paired owner `llblab`. Impact: the repaired remote-peer attribution now has direct Telegram evidence, so the completed P0 moved out of `BACKLOG.md` and the tree is ready for the next hotfix scope.
26
+
3
27
  ## 0.20.5: Guest Media And Runtime Recovery Hotfix
4
28
 
5
29
  - `[Setup Persistence]` The setup prompt now applies the validated bot identity to its config store before invoking persistence, so adapters that serialize current store state cannot write an empty or stale `telegram.json` on first run. Persistence failure rolls the in-memory candidate back and remains ahead of success notifications and polling startup. Impact: setup from a missing file or `{}` durably writes `botToken`, `botId`, and `botUsername` on the first successful command for default and named profiles without reporting an unsaved connection.
package/README.md CHANGED
@@ -24,6 +24,8 @@ From git:
24
24
  pi install git:github.com/llblab/pi-telegram
25
25
  ```
26
26
 
27
+ The 0.21 extension platform requires Pi `0.80.6` or newer. Its Activity API uses the public `agent_settled` lifecycle event to keep retries/continuations under one activity identity and release that identity only after the run fully settles.
28
+
27
29
  ## Quick Start
28
30
 
29
31
  ### 1. Create a Telegram bot
@@ -217,12 +219,14 @@ Companion extensions can integrate with Telegram without owning polling or trans
217
219
  - Register Telegram slash commands.
218
220
  - Add menu sections and settings surfaces.
219
221
  - Add compact status rows.
222
+ - Deliver target-aware operational views and chat actions from companion code.
223
+ - Observe normalized assistant, reasoning, tool, compaction, and settlement activity without blocking Pi.
220
224
  - Handle update/callback namespaces.
221
225
  - Provide inbound preprocessing handlers.
222
226
  - Provide outbound voice synthesis.
223
227
  - Use direct delivery helpers for explicit local/TUI sends.
224
228
 
225
- Stable public entrypoints are documented in [Public API](./docs/public-api.md), [Extension Sections](./docs/sections.md), [Inbound Handlers](./docs/inbound.md), [Outbound Handlers](./docs/outbound.md), [Updates](./docs/updates.md), and [Voice Integration](./docs/voice.md).
229
+ Stable public entrypoints are documented in [Public API](./docs/public-api.md), [Telegram Delivery API](./docs/delivery.md), [Telegram Activity API](./docs/activity.md), [Extension Sections](./docs/sections.md), [Inbound Handlers](./docs/inbound.md), [Outbound Handlers](./docs/outbound.md), [Updates](./docs/updates.md), and [Voice Integration](./docs/voice.md).
226
230
 
227
231
  ## Safety Boundaries
228
232
 
@@ -241,6 +245,8 @@ Telegram is a companion surface around a live Pi runtime, not a second runtime.
241
245
 
242
246
  - [Architecture](./docs/architecture.md) — runtime, domains, queue, transport, and Threaded Mode overview.
243
247
  - [Public API](./docs/public-api.md) — package entrypoints and stable companion-extension contracts.
248
+ - [Telegram Delivery API](./docs/delivery.md) — target-aware operational views, logical message handles, and lifecycle-safe transport.
249
+ - [Telegram Activity API](./docs/activity.md) — normalized lifecycle events, source identity, non-blocking delivery contexts, and consumer policy examples.
244
250
  - [Inbound Handlers](./docs/inbound.md) — Telegram-to-Pi preprocessing pipelines.
245
251
  - [Outbound Handlers](./docs/outbound.md) — final text/voice/file transformation and delivery.
246
252
  - [Voice Integration](./docs/voice.md) — STT/TTS provider model and reply policies.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Public Telegram activity API
3
+ * Zones: package boundary, pi agent lifecycle, extension interop
4
+ * Exposes normalized non-blocking activity registration while keeping lifecycle wiring and dispatch internals package-private
5
+ */
6
+
7
+ export {
8
+ registerTelegramActivityHandler,
9
+ type TelegramActivityContext,
10
+ type TelegramActivityEnvelope,
11
+ type TelegramActivityEvent,
12
+ type TelegramActivityHandlerRegistration,
13
+ type TelegramActivitySource,
14
+ type TelegramActivityTarget,
15
+ } from "../lib/activity.ts";
package/api/commands.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public Telegram commands API
3
- * Zones: package boundary, companion extension interop
3
+ * Zones: package boundary, extension interop
4
4
  * Exposes the stable Telegram slash-command registration surface while keeping registry internals package-private
5
5
  */
6
6
 
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Public Telegram delivery API
3
+ * Zones: package boundary, telegram delivery, extension interop
4
+ * Exposes target-aware operational view delivery while keeping transport and runtime binding internals package-private
5
+ */
6
+
7
+ export {
8
+ deleteTelegramView,
9
+ editTelegramView,
10
+ sendTelegramChatAction,
11
+ sendTelegramView,
12
+ type SendTelegramViewOptions,
13
+ type TelegramDeliveryChatAction,
14
+ type TelegramDeliveryFailureReason,
15
+ type TelegramDeliveryHandle,
16
+ type TelegramDeliveryParseMode,
17
+ type TelegramDeliveryResult,
18
+ type TelegramDeliveryScope,
19
+ type TelegramDeliveryTarget,
20
+ type TelegramDeliveryView,
21
+ } from "../lib/delivery.ts";
package/api/inbound.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public Telegram inbound API
3
- * Zones: package boundary, companion extension interop
3
+ * Zones: package boundary, extension interop
4
4
  * Exposes the stable programmatic inbound handler surface while keeping handler runtime internals package-private
5
5
  */
6
6
 
package/api/keyboard.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public Telegram keyboard API
3
- * Zones: package boundary, companion extension interop
3
+ * Zones: package boundary, extension interop
4
4
  * Exposes shared inline-keyboard structural types without runtime exports
5
5
  */
6
6
 
package/api/outbound.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public Telegram outbound API
3
- * Zones: package boundary, companion extension interop
3
+ * Zones: package boundary, extension interop
4
4
  * Exposes stable outbound handler and diagnostics surfaces while keeping delivery internals package-private
5
5
  */
6
6
 
package/api/sections.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public Telegram sections API
3
- * Zones: package boundary, companion extension interop
3
+ * Zones: package boundary, extension interop
4
4
  * Exposes the stable managed Telegram menu-section surface while keeping registry internals package-private
5
5
  */
6
6
 
package/api/status.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Public Telegram status API
3
- * Zones: package boundary, companion extension interop
4
- * Exposes compact status-menu line registration for companion extensions while keeping status rendering internals package-private
3
+ * Zones: package boundary, extension interop
4
+ * Exposes compact status-menu line registration for extension consumers while keeping status rendering internals package-private
5
5
  */
6
6
 
7
7
  export {
package/api/updates.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public Telegram updates API
3
- * Zones: package boundary, companion extension interop
3
+ * Zones: package boundary, extension interop
4
4
  * Exposes the stable raw-update handler surface while keeping update routing internals package-private
5
5
  */
6
6
 
package/api/voice.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Public Telegram voice API
3
- * Zones: package boundary, companion extension interop
3
+ * Zones: package boundary, extension interop
4
4
  * Exposes the stable STT/TTS provider surface and voice policy helpers
5
5
  */
6
6
 
package/docs/README.md CHANGED
@@ -8,6 +8,8 @@ Living index of project documentation in `/docs`.
8
8
 
9
9
  - [architecture.md](./architecture.md) — Overview of the Telegram bridge runtime, optional profile isolation, queueing model, native Rich Markdown delivery, UI/compat rendering, and interactive controls
10
10
  - [public-api.md](./public-api.md) — Stable public API map: package entrypoints, commands, config, assistant markup, extension APIs, smoke examples, and compatibility boundaries
11
+ - [delivery.md](./delivery.md) — Target-aware companion delivery contract for operational views, logical message handles, target scopes, lifecycle fencing, and leader/follower transport
12
+ - [activity.md](./activity.md) — Normalized Pi lifecycle API for extension-owned reasoning, intermediate prose, tool activity, source identity, delivery contexts, and consumer policy examples
11
13
  - [../.agents/skills/telegram-bot/SKILL.md](../.agents/skills/telegram-bot/SKILL.md) — Agent-facing Telegram Bot API lookup skill backed by a local full Bot API reference
12
14
  - [../.agents/skills/domain-dag/SKILL.md](../.agents/skills/domain-dag/SKILL.md) — Project-local Domain DAG architecture skill and validator guidance
13
15
  - [command-templates.md](./command-templates.md) — Portable command-template standard core