@llblab/pi-telegram 0.23.0 → 0.23.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 CHANGED
@@ -41,6 +41,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
41
41
  - `/AGENTS.md`: Durable engineering and runtime conventions
42
42
  - `/BACKLOG.md`: Canonical open work. Keep only open top-level tasks; when all subtasks under a top-level task are complete, remove that task from the backlog and record completed delivery in `CHANGELOG.md` if user-visible. Put detailed decomposition under the single owning top-level task with nested checkboxes and explicit done criteria instead of promoting completed slices into separate top-level backlog items.
43
43
  - `/CHANGELOG.md`: Completed delivery history focused on the final released behavior and user/operator/developer impact. Prefer multiple domain-scoped bullets with an inline-code domain label followed by a colon, change, and impact—for example, `Lock Fencing`: change + impact—instead of accumulating unrelated changes into one long entry; never use square brackets for the domain label. Never include personal usernames, user/chat ids, message/thread ids, or operator-specific thread labels in changelog/docs evidence; use role placeholders such as `<remote-peer>`, `<paired-owner>`, or `assigned leader thread`. Do not record transient implementation churn such as "added then removed" mechanics, internal reversions, or cleanup of an abandoned intermediate path unless the final product surface exposes that as a meaningful migration/breaking change.
44
+ - `Pre-release changelog consolidation`: Treat the current version section as a working set during development, then consolidate it before the release commit, PR, tag, and GitHub Release. Merge repeated implementation and smoke chronology into the smallest truthful set of outcome-focused domain bullets; preserve distinct shipped behavior, safety/compatibility contracts, migrations, known limitations, and meaningful operator evidence. Remove superseded findings, repeated validation runs, and fixed-then-reworked mechanics. Do not rewrite older release sections during this gate.
44
45
 
45
46
  ## 4. Core Entities
46
47
 
@@ -85,11 +86,9 @@ The core product loop is mobile continuation: start or supervise work in the ter
85
86
  - Follower registration readiness should stay on the smallest critical path that makes the follower routable: create or reuse the Telegram target, persist the active binding, mark target-bindings fresh, and return the target to the follower. Registration requires a present exact generation. Cross-session target reuse and any carried target absent from current bindings require one synchronous visibility probe: explicit stale evidence may provision a replacement, while ambiguous/non-stale failure must reject registration without replay, binding invalidation, or speculative replacement. Persist an ambiguously observed absent target only as non-routable `probe-required` restoration evidence so targetless retries and leader reloads must probe the exact target again before activation or replacement. Registration and explicit disconnect mutations for one durable follower profile must serialize across awaited cleanup so an old runtime's disconnect cannot delete or unregister a replacement generation. Manual follower identity must survive a Pi process reload in the same terminal so reload is not treated as a new follower/thread; do not key durable follower ownership only by the short-lived Pi process id. Telegram-visible connected notices, duplicate/replaced thread cleanup, and other reconciliation work are important but non-critical; run them after registration and record slow/failing background steps as runtime events rather than delaying follower usability.
86
87
  - Instance slots are extension-owned ring-order metadata. Allocate fresh slots from the `bot.lastSlot` cursor as `A` → `B` → ... → `Z` → `A`, allowing the cursor to move to lower letters after wraparound. Only current live/recovering bindings plus unexpired pending provisions and explicit reservations occupy slots; historical records must not block allocation indefinitely. Preserve a slot on ordinary current binding/thread reuse without advancing or rewinding the fresh-allocation cursor. Follower bindings survive process absence as stable restoration hints until explicit stale/deleted/offline evidence invalidates them. Successful target reuse must refresh the binding timestamp and replacement runtime identity; generic leader startup or heartbeat pruning must not erase the target merely because no follower registration is currently live. An authenticated live follower whose carried exact target is missing from current persisted bindings should recover that target instead of creating another Telegram thread; preserve a carried slot only when it does not collide with another current record. Preserve a cursor that belongs to a live or recoverable binding and continue skipping retained restart hints plus unexpired pending/reserved slots. Explicit stale/deleted/offline reconciliation may release that slot; generic process absence may not. The alphabet cap is a feature — compact single-letter tabs are scannable.
87
88
  - 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.
88
- - 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`.
89
- - 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.
90
- - 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. 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.
89
+ - 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.
90
+ - 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.
91
91
  - 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.
92
- - Cross-session follower binding reuse must surface the target with one compact connected probe before registration succeeds. Explicit stale-topic rejection may replace the target with the next monotonic slot; ambiguous/non-stale send failure must not replay, replace, or claim proof that the tab is stale.
93
92
  - 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.
94
93
  - 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.
95
94
  - Target-scoped behavior must preserve `{ chatId, threadId? }` through inbound messages/edits/callbacks/reactions, thread lifecycle service messages, media and split-text grouping, queue mutations, active-turn cleanup, previews, reply deduplication, menus, sections, generated buttons, voice, attachments, and direct delivery. Threaded Mode replies must keep Telegram's reply affordance to the triggering message just like classic DM mode. In Telegram private-chat Threaded Mode, mobile Telegram has been live-verified to render `sendMessage` replies that include top-level `message_thread_id` plus same-chat `reply_parameters.message_id`; Telegram Desktop may fail to show the visual reply header for the same correct payload, so do not treat Desktop-only absence as bridge failure without mobile/payload evidence.
@@ -117,12 +116,12 @@ The core product loop is mobile continuation: start or supervise work in the ter
117
116
  - `telegram_attach` is the canonical outbound file-delivery path for Telegram-originated requests; outside active Telegram turns it may send immediately to the paired/default chat, a registered follower's assigned target, or explicit `chat_id` plus optional `thread_id` for explicit local/TUI delivery requests only when this Pi instance owns `/telegram-connect` or is registered with the Threaded Mode multi-instance bus. `telegram_message` is the first-class direct Telegram Markdown text tool for local/TUI prompts and follows the same ownership-or-bus-registration gate; neither direct tool replaces normal active-turn replies. It reuses top-level `telegram_button` comments for inline buttons; buttons must be attached to a text message, never sent as standalone actions
118
117
  - Telegram prompt guidance is layered and context-frugal: unconfigured sessions receive no bridge suffix, configured sessions receive compact direct-delivery/help guidance, and Telegram-originated turns receive only the minimal per-turn contract. Detailed voice/button/direct-delivery/Threaded Mode/formatting/debug guidance belongs in the agent-facing `telegram_help` tool instead of being repeated in every prompt.
119
118
  - Telegram inbound prompt topology keeps current user material contiguous: `[outputs]` produced from current attachments follows `[attachments]` immediately, then independent `[reply]` or forwarded-source context follows the complete current-user block. Forwarded Rich Message media belongs under source-attributed `[attachments|from:<identity>]` immediately after its `[forward|from:<identity>]` block and remains available as model image content. Do not place source context between an attachment and its transcription or handler output.
120
- - Forward-comment grouping uses a bounded one-second candidate window for ordinary short human text so an adjacent same-sender/chat/thread forward can join across polling responses; a matching forward flushes immediately. Commands, bots, captions, media groups, target/sender mismatches, reversed ids, and distant message ids bypass grouping. Preserve the grouped candidate across session replacement and follower forwarding without weakening exact routing-generation authority.
119
+ - 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.
121
120
  - `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
122
- - Voice reply policy and prompt context are owned by pi-telegram's `telegram.json` `voice.replyMode`: missing/invalid config behaves as `manual` but does not add a `[voice]` prompt-context block; only an explicit valid `voice.replyMode` renders context. Render a single voice field as `[voice] reply mode: manual|mirror|always`, and render multiple fields as a `[voice]` list; place voice context after `[outputs]` when handler output exists, otherwise after `[attachments]`; provider prompt contributions are optional provider-specific additions, not the default policy channel
121
+ - 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.
123
122
  - Optional `telegram.json` `time` may add `[time] YYYY-MM-DD HH:mm:ss <timezone>` to Telegram-originated prompts for wall-clock context. It is hidden by default, uses `time.injectionMode` values `hidden|always|interval`, stores `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 `time.injectionMode` only.
124
123
  - 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. 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.
125
- - Voice reply mode Settings UI standard: the top-level Settings row is `👄 Voice reply: hidden|manual|mirror|always`; `hidden` is the true default and means no valid `voice.replyMode` is persisted, behavior is manual, and no voice policy is added to prompt context; explicit `manual` behaves the same operationally but renders reply-mode context. 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
124
+ - 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
126
125
  - 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
127
126
  - `telegram_voice` text is arbitrary TTS-target text and the prompt should show only forms that include text to speak: `<!-- telegram_voice text="Short summary" -->` or `<!-- telegram_voice: Short summary -->`. Avoid raw Markdown/code/tables in voice text.
128
127
  - `telegram_button` has three canonical forms: `<!-- telegram_button: OK -->` for label-only buttons, `<!-- telegram_button label=Continue prompt="Continue with the current plan." -->` for one-line prompts, or `<!-- telegram_button label="Show risks"\nList the main risks first.\n-->` for multiline prompts. Do not author JSON button specs, inline-after-text comments, standalone button tools, or comments inside code/quotes/lists/indented examples; write normal Markdown plus top-level hidden comments, and add visible parent text when buttons would otherwise be the only output
@@ -138,18 +137,26 @@ The core product loop is mobile continuation: start or supervise work in the ter
138
137
  - For non-trivial implementation, release, or architecture-audit work, run an `AGENTS.md` compliance pass: reread the durable principles and relevant project docs, map the changed behavior to the rules it touches, validate code/tests/docs against those rules, and explicitly surface any rule conflict, obsolete rule, or evidence-backed improvement. When the rules themselves drift from the product reality, update `AGENTS.md` or document a deliberate exception in the same pass instead of silently working around it.
139
138
  - Treat Windows Telegram runtime error reports as high-signal evidence even when Linux is the primary development environment. Windows uses different filesystem/IPC behavior and is more likely to reveal lock, heartbeat, named-pipe, atomic-rename, and Threaded Mode edge cases; minimize each report into a regression or a documented platform caveat instead of dismissing it as environment noise.
140
139
 
141
- ## 6.2 Validation Hotspots
140
+ ## 6.2 Agent Context Discipline
141
+
142
+ - Keep successful command output compact at the tool boundary. Redirect full validation logs to a temporary file, report only the command status and a small summary, and inspect a bounded failure tail or targeted range only when the command fails. `npm test` uses the dot reporter for this default; use `npm run test:verbose` only when individual test names provide necessary diagnostics.
143
+ - Read large artifacts search-first and range-bounded. Prefer path/content search, exact line ranges, `git diff --stat`, and path-scoped diffs over whole large files or complete repository diffs; the vendored Bot API reference must follow its stricter skill-local lookup contract.
144
+ - Treat `CHANGELOG.md` as a large artifact: read only the first 50 lines for the latest release by default; for an older release, locate its heading first and read only that bounded section. Never read the complete changelog merely to inspect one release.
145
+ - During implementation, prefer focused typechecks and tests. Run the complete validation suite only at a meaningful stable gate such as final review, release preparation, or after a cross-cutting correction.
146
+ - Bound independent review context: use focused reviewers for unresolved risks, then one complete independent review after implementation stabilizes. Do not repeatedly rebuild multiple full-project reviewer contexts without new evidence.
147
+
148
+ ## 6.3 Validation Hotspots
142
149
 
143
150
  - Treat queue handling, compaction interaction, and lifecycle-hook state transitions as regression-prone areas; validate them after changing dispatch logic
144
151
  - Keep the standard `npm run typecheck` gate strict about unused locals and parameters so stale imports and abandoned adapters fail validation instead of accumulating silently
145
- - Route important runtime failures through the recent runtime event recorder so `/telegram-status` remains useful for post-mortem debugging, not just transient status-bar errors
152
+ - Route important runtime failures through the recent runtime event recorder so `/telegram-status` remains useful for post-mortem debugging. Keep the compact TUI status bar bounded to the generic `error` state; detailed error messages belong only in diagnostics/logs, never inline beside the status label.
146
153
  - Treat remaining Markdown-to-HTML rendering as Telegram UI/compat output work, not generic Markdown rendering or assistant reply delivery
147
154
  - Preserve literal code content in Telegram rendering
148
155
  - Avoid HTML chunk splits that break tags
149
156
  - Prefer width-efficient monospace table and list formatting for narrow clients, with table padding based on grapheme/display width rather than raw UTF-16 length where possible
150
157
  - Flatten nested Markdown quotes into indented single-blockquote output because Telegram does not render nested blockquotes reliably
151
158
 
152
- ## 6.3 File And Naming Style
159
+ ## 6.4 File And Naming Style
153
160
 
154
161
  - Keep comments and user-facing docs in English unless the surrounding file already follows another convention
155
162
  - Each project `.ts` file should start with a short multi-line responsibility header comment that explains the file boundary to future maintainers; source-module headers must include `Zones:` tags for cross-cutting responsibility areas
@@ -160,7 +167,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
160
167
  - Keep composition wiring DRY with small local adapters or owning-domain contracts when repetition appears, but do not hide live mutable session state behind broad facades just to reduce repeated closures
161
168
  - Keep interface contracts consistent for the same runtime entity: prefer the owning domain's exported contract when multiple modules mean the same entity, and use local structural `*Like`/view contracts only for deliberate narrow projections that avoid real coupling without duplicating source-of-truth shapes
162
169
 
163
- ## 6.4 Current Domain Ownership Snapshot
170
+ ## 6.5 Current Domain Ownership Snapshot
164
171
 
165
172
  The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/architecture.md). Keep this section as a compact agent-facing index, not a second copy of the full map.
166
173
 
@@ -171,7 +178,7 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
171
178
  - 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
172
179
  - 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
173
180
 
174
- ## 6.5 Entrypoint And Import Boundaries
181
+ ## 6.6 Entrypoint And Import Boundaries
175
182
 
176
183
  - Keep the preview domain as a thin streaming lifecycle controller only: draft ids, safe-prefix selection for `sendRichMessageDraft`, voice suppression, serialized flushes, diagnostics, and finalization state. Do not reintroduce assistant preview rendering there; keep `rendering.ts` scoped to bridge-owned UI/compat regular-message rendering rather than assistant or guest Markdown delivery. Drafts must only send structurally closed Markdown prefixes; draft failures are not proof that drafts are globally unsupported, so record the failure and skip that preview frame. Do not add raw plain-message fallback previews for assistant Markdown
177
184
  - Preview/final delivery ordering is release-critical: finalization waits for active preview flushes, persisted final delivery should not be followed by a post-final draft-clear call that creates transient draft UI, and regressions should cover in-flight draft flush serialization plus final reply ordering.
package/BACKLOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Project Backlog
2
2
 
3
- _This backlog tracks only open release-relevant work: hotfixes, 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._
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
5
  ## P1 — Native Windows Threaded Mode Follow-Ups
6
6
 
package/CHANGELOG.md CHANGED
@@ -1,53 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.23.2: Voice Policy And Turn Delivery Hotfix
4
+
5
+ - `Settings Persistence Race`: Rebuilt expired Settings callback state from the live model-menu context before applying mutations. Polling now persists only its monotonic `lastUpdateId` into the current config-store snapshot instead of later submitting the detached full config object captured when polling started. Impact: a subsequent Telegram update can no longer erase freshly persisted `voice.replyMode`, `assistant.proactivePush`, or other Settings values and return the menu to stale defaults.
6
+ - `Compact Error Status`: Reduced pi-telegram's TUI error projection to the single `error` state while retaining detailed failures in runtime diagnostics and logs. Impact: provider and transport messages no longer consume the status line or displace the surrounding model/session indicators.
7
+ - `Forward Annotation Pairing`: Extended the bounded one-second coalescer so one optional owner-authored annotation and adjacent forward join in either transport order, including photo-only forwards without their own source caption. Prompt construction keeps the layers distinct: owner annotation first, then `[forward|from:...]` with the forward's own source text/caption, then source-attributed media; same-kind messages and existing command/sender/target/id-gap exclusions remain separate. Impact: Telegram forwarding gestures no longer split the user's explanation from the forwarded source or conflate it with the forward's own content.
8
+ - `Voice Reply Policy`: Collapsed the redundant explicit `manual` mode into the silent `hidden` default, kept `mirror` automatic only for voice/audio input, and kept `always` automatic for every Telegram turn. Active automatic turns now carry exactly `[voice] delivery: automatic voice`; the per-turn system suffix only points to `telegram_help`, explicit `telegram_voice` remains an agent-authored override, and legacy `manual` config resolves to `hidden`. Impact: models receive one effective delivery fact instead of a noisy mode matrix, while providers remain responsible for synthesis rather than text/voice composition.
9
+ - `Provider Retry Delivery`: Retained an active Telegram turn across low-level `agent_end` errors until Pi either produced a successful retry result or emitted `agent_settled`. Retry recovery now delivers the eventual semantic result once to the original target, while an exhausted retry finalizes the retained error once and releases queue state; redacted diagnostics distinguish retention, recovery, and settled failure. Impact: transient provider transport failures can no longer orphan a later successful reply from its originating Telegram turn.
10
+
11
+ ## 0.23.1: Context Budget And Runtime Simplification
12
+
13
+ - `Bus Runtime Simplification`: Removed the no-op follower-binding recovery timer and grace configuration, the unused disconnected-announcement helper, and the duplicate follower-prune callback. Heartbeat pruning now emits one accurate diagnostic while preserving durable thread bindings. Impact: leader/follower recovery carries less dormant state and cannot imply offline cleanup that never occurs.
14
+ - `Thread Reconciliation Safety`: Removed unreachable reservation-probe/removal machinery and the uncalled heartbeat-prune destructive cleanup action. Explicit disconnect, replacement, previous-leader, and expired-provision cleanup retain their confirmation and leader-epoch fences. Impact: reconciliation matches the documented rule that heartbeat loss removes live routing authority without deleting a follower's recoverable Telegram thread.
15
+ - `Destination Resolution`: Collapsed proactive chat-id and target selection onto the target resolver's active-turn → assigned-thread → paired-chat priority, retaining a scalar adapter only where an API requires a chat id. Impact: proactive projection, activity typing, and Guest attachment staging cannot drift between duplicate destination sources.
16
+ - `Agent Context Budget`: Added compact successful-test output with an opt-in verbose reporter, bounded failure-log inspection, search-first large-artifact and Bot API lookup rules, scoped diff/review guidance, and stable-gate validation policy. Compressed repeated agent, architecture, and multi-instance contracts without weakening their safety meaning. Impact: ordinary validation and review consume substantially less model context while actionable diagnostics remain available in retained logs.
17
+ - `Validation`: Full typecheck/tests/audit/package dry-run, strict Domain DAG, ABCd context validation, repository hygiene checks, and an independent regression review all passed after cleanup. Impact: the behavior-preserving hotfix is release-ready with no identified blockers.
18
+
3
19
  ## 0.23.0: Telegram Bot API 10.2 Rich Output And Proactive Projection
4
20
 
5
- - `Follower Tab Visibility`: Added one synchronous connected probe when a follower reuses a target across session identity or carries an exact target absent from current bindings. A successful probe surfaces or recovers the existing Telegram tab; explicit stale-topic rejection marks the old target deleted and provisions a monotonic replacement before registration succeeds, while other failures—including ambiguous non-idempotent acknowledgement—preserve non-routable probe-required restoration evidence and reject registration without replay or speculative replacement; targetless retries and leader reloads must probe that exact target again. Impact: a follower cannot silently report an invisible or post-disconnect deleted target as restored, and stale replacements advance rather than resetting the slot sequence.
6
- - `Explicit Thread Disconnect`: Added an authenticated follower disconnect envelope, required present exact generations for registration and disconnect, serialized both mutations per durable follower profile across awaited cleanup, and added a thread-named destructive confirmation in Threaded Mode. Cleanup now reports incomplete actions: only successful deletion or explicit already-gone evidence permits offline persistence, routing-authority removal, and acknowledgement; otherwise leader, promoted leader, or follower state remains available with actionable retry guidance. Impact: `/telegram-disconnect` removes the current Telegram tab consistently without letting an old runtime delete or unregister a replacing follower or falsely report success while the tab remains visible.
7
- - `Three-Instance Topology Smoke`: Repeated the live three-instance connect, visibility, failover, and promoted-leader reload scenario. Every follower surfaced a usable tab and the resulting topology remained healthy; observed promotion order still depended occasionally on runtime timing, which the operator accepted because exact atomic ownership and bounded failover remained intact. Impact: follower visibility and multi-instance recovery have direct operator evidence without misrepresenting the lowest-slot grace as a strict consensus election.
8
- - `Lowest-Slot Succession`: Added the authenticated live follower-slot roster to heartbeat acknowledgements. During failover, a follower whose slot has a lower live predecessor defers for one bounded election grace and then rechecks the atomic lock; the lowest observed slot proceeds immediately, while a dead lower candidate can delay but never indefinitely block a higher survivor. Impact: clean multi-follower leader loss usually favors `C` before `D` without replacing exact lock fencing with timing-only ownership or introducing a consensus phase.
9
- - `Promoted Leader Reload Live Smoke`: Confirmed that a follower promoted to leader records `follower-promoted-session-handoff`, reload restores the exact prior target/slot/name, and leader startup reports `reused: true` without creating a replacement tab. Impact: the original promoted-leader identity-loss blocker is closed with direct runtime evidence; the same three-instance smoke separately exposed follower-tab visibility and deterministic successor-order follow-ups.
10
- - `Promoted Leader Reload Handoff`: Retained the promoted target, slot, and thread name immediately after successful election and refreshed that short-lived process-local handoff before leader session suspension, binding both paths to the exact Telegram profile owner key. Restoration persists the binding under acquired leader authority before topic provisioning. The fallback identity source includes the current instance's inherited manual-follower record even after registration stops, because repeated live diagnostics proved leader-only/default identity projection remained empty and no shutdown handoff event was emitted. The same restoration projection now keeps the inherited thread name visible as `Cinder electing`-style status instead of regressing to generic `telegram electing`. Impact: promotion and election status retain the existing instance identity, while promoted leader reload reclaims the same Telegram thread instead of resetting to slot `A` and allocating a new tab, as confirmed by the promoted-leader reload smoke.
11
- - `Leader/Follower Election Cycle Smoke`: Confirmed live with two instances that leader disconnect promotes the registered follower, reconnecting the previous leader registers it as follower, disconnecting the promoted leader promotes the remaining follower back, and reconnecting the second instance restores it as follower. Impact: repeated bidirectional election, promotion, disconnect, and re-registration converge without manual takeover or a stranded topology.
21
+ - `Follower Visibility And Restoration`: Added one synchronous connected probe before cross-session target reuse or recovery of an exact target absent from current bindings. Success surfaces the existing tab; explicit stale-topic evidence provisions a monotonic replacement, while ambiguous failures preserve non-routable `probe-required` evidence and reject without replay or speculative replacement. Impact: followers cannot report invisible or deleted tabs as restored, and retries must resolve the exact target before activation.
22
+ - `Explicit Thread Disconnect`: Authenticated registration and disconnect with present exact generations, serialized both mutations per durable follower profile across awaited cleanup, and required thread-named confirmation plus successful deletion or explicit already-gone evidence before removing routing authority. Incomplete cleanup preserves leader/follower state with retry guidance, and promoted leaders authorize inherited cleanup through their current epoch. Impact: `/telegram-disconnect` cannot let an old runtime delete a replacement registration or falsely report success while its tab remains.
23
+ - `Follower Succession And Handoff`: Added the authenticated live slot roster to heartbeats so the lowest observed follower proceeds immediately on leader loss while higher slots defer for one bounded grace and then recheck exact atomic ownership. Promoted followers retain target, slot, thread name, profile ownership, and electing status through same-process session handoff; reload persists that identity under acquired leader authority before provisioning. Impact: failover prefers slot order without claiming consensus, dead lower candidates cannot block recovery indefinitely, and promoted reload reuses the existing tab instead of resetting identity.
24
+ - `Threaded Topology Evidence`: Two- and three-instance live smokes confirmed usable follower tabs, repeated bidirectional promotion/disconnect/re-registration, promoted-leader reload reuse, and healthy convergence without manual takeover. Occasional timing-dependent promotion order remains compatible with bounded grace because exact atomic ownership—not timing—fences leadership. Impact: visibility, restoration, and multi-instance recovery have direct operator evidence without overstating succession guarantees.
12
25
  - `Composition Root Compression`: Reduced `index.ts` from 1,119 to 1,083 lines by moving assistant-output admission/authority/sender coordination into the existing Pi-binding orchestration domain, moving config-persist sync sequencing and stale-topic API recovery adapters into `sync`, and replacing local forwarding callbacks with direct live-port wiring where signatures already matched. Impact: the entrypoint retains visible runtime composition while low-level policy, failure projection, mutable sequencing, and reusable adapters stay with their owning domains.
13
- - `Forward Comment Live Smoke`: After reload, confirmed a short comment and a Rich-image forward arriving through the active Telegram thread produce one Pi turn ordered as comment → `[forward|from:…]``[attachments|from:…]`, with the downloaded image present as model input. Impact: cross-poll grouping and forwarded Rich media attribution now have direct live evidence in addition to deterministic coverage.
14
- - `Cross-Batch Forward Comments`: Added a bounded one-second candidate window for ordinary short human text and immediate flush when the adjacent same-sender/chat/thread forwarded message arrives, including Rich Message text and follower-routed updates. Impact: a comment and its forward form one Pi turn across separate long-poll responses instead of relying on incidental Telegram batching.
15
- - `Forwarded Rich Attachments`: Extracted photo, video, animation, audio, and voice-note files from nested inbound `rich_message` blocks and rendered them under source-attributed attachment context immediately after the corresponding forward block. Impact: forwarded Rich images remain visible to the model as image content without being mislabeled as current-user attachments.
16
- - `Follower Rich Attachment Smoke`: Confirmed from an assigned follower Telegram topic that one queued PNG plus final text arrives as one reply-anchored Rich Message, with no separate attachment notice, second upload, or duplicate final. Impact: Bot API 10.2 one-result media delivery now has live client evidence across both direct-leader and follower transports.
17
- - `Follower Bidirectional Routing`: Propagated the exact live follower registration generation from target ownership into every leader-forwarded message envelope and recorded rejected forwarding acknowledgements. Impact: follower threads can receive Telegram prompts again without weakening replacement-generation fencing, and a future route rejection leaves actionable diagnostics instead of disappearing silently.
18
- - `Follower Thread Restoration`: Preserved stable manual-follower owner/target bindings across process absence and changed replacement registration to reclaim the existing current thread rather than creating another same-named tab. Persisted bindings remain restart hints only; authenticated live registration remains mandatory routing authority, while explicit stale/deleted/offline evidence still invalidates reuse. Impact: restarting the same follower restores its recognizable thread and slot without allowing absent processes to receive work.
19
- - `Inbound Prompt Topology Smoke`: Confirmed in a live voice reply to an image message that the current voice attachment and its transcription appear before the independent `[reply]` block and replied-message attachment context. Impact: live prompt evidence matches the deterministic current-user-first topology without separating an attachment from its derived output.
20
- - `Inbound Prompt Topology`: Kept attachment-derived `[outputs]`, including voice transcription, immediately after the current user `[attachments]` block and moved independent reply/source context after the complete current-user material. Impact: attachment meaning remains topologically connected to the user input that produced it instead of being split by quoted-message context.
26
+ - `Forwarded Input`: Added a bounded one-second candidate window that joins ordinary short comments with an adjacent same-sender/chat/thread forward across polling responses, including Rich Message text and follower-routed updates. Nested forwarded photo, video, animation, audio, and voice-note media now render under source-attributed attachments immediately after the forward block. A post-reload live smoke confirmed comment → forward → attachment ordering with the image available as model input. Impact: comments and forwards form one correctly attributed Pi turn without relying on incidental polling batches or mislabeling forwarded media as current-user attachments.
27
+ - `Follower Routing And Restart`: Propagated the exact live registration generation into every leader-forwarded envelope, recorded rejected acknowledgements, and preserved stable manual-follower bindings across process absence as restart hints only. Replacement registration reclaims the current recognizable thread and slot, while authenticated live registration remains mandatory routing authority and explicit stale/deleted/offline evidence invalidates reuse. Impact: follower prompts remain generation-fenced and diagnosable, and restarts avoid duplicate same-named tabs without routing to absent processes.
28
+ - `Inbound Prompt Topology`: Kept attachment-derived `[outputs]`, including voice transcription, immediately after current-user `[attachments]`, with independent reply/source context following the complete current-user block. A live voice reply to an image confirmed the same current-user-first ordering. Impact: attachment meaning remains connected to the input that produced it instead of being split by quoted-message context.
21
29
  - `Portable Lock Standard`: Synchronized `docs/locks.md` bit-for-bit with the reusable cross-project standard and removed Telegram-specific runtime, bus, profile, and release-history policy from that portable artifact. Impact: the standard can be copied into another singleton-extension project as a complete implementation contract, while project-specific behavior remains owned by architecture documentation.
22
- - `Proactive Default`: Enabled `assistant.proactivePush` by default when the nested setting is absent while preserving nested explicit `false` as the canonical opt-out. Updated settings copy and operator documentation for completed public block projection. Impact: connected local/autonomous work reaches Telegram without an extra setup toggle, while operators retain a direct disable switch.
23
- - `Assistant Config`: Moved the proactive local-result delivery switch under `assistant.proactivePush` beside assistant rendering and draft-preview policy, without retaining the removed top-level key as a compatibility alias. Impact: configuration and Settings UI use one coherent assistant namespace; operators who want projection disabled set `assistant.proactivePush` to `false`.
30
+ - `Proactive Configuration And Documentation`: Moved local/autonomous projection policy under `assistant.proactivePush` beside assistant rendering and draft-preview settings, enabled it when absent, retained nested explicit `false` as the canonical opt-out, and intentionally omitted a compatibility alias for the removed top-level key. Settings explain `on` (default) and `off`, while README, Activity, Outbound, Public API, architecture, and durable contracts document ordered public-block projection, exclusions, rendering, authority, follower routing, and no-replay behavior. Impact: configuration, operator copy, and maintainer contracts describe one coherent default-enabled feature with an explicit disable path.
24
31
  - `Config Transactions`: Valid Telegram configuration snapshots now load directly from the atomically published file, while malformed-file recovery and every merge/write remain serialized by the cross-process transaction guard. Impact: ordinary `telegram.json` reads no longer create transaction-directory churn, without weakening concurrent persistence or destructive recovery safety.
25
- - `Proactive Projection Documentation`: Updated the README, Activity, Outbound, Public API, architecture ownership map, and durable project contract to define `assistant.proactivePush` as ordered projection of every completed public local/autonomous text block rather than a final-only notification. Impact: commentary/final visibility, hidden reasoning/tool exclusions, Rich/HTML behavior, exact authority fences, follower routing, and no-replay semantics now have one consistent operator and maintainer contract.
26
32
  - `Release Preflight`: Ran the complete 0.23.0 validation suite after follower visibility, bounded succession preference, explicit cross-role thread disconnect, cross-batch grouping, and forwarded Rich media fixes: 1,292 tests passed with one platform-only skip, typecheck passed, npm audit reported zero vulnerabilities, package dry-run produced `@llblab/pi-telegram@0.23.0`, and `git diff --check` stayed clean. Impact: deterministic, packaging, dependency, and repository hygiene gates are green, and the final independent release review reported SHIP with no blockers.
27
- - `Follower Rich Smoke Classification`: Confirmed the first follower live attempt originated from the follower TUI rather than its Telegram topic. With no active Telegram turn, `telegram_attach` correctly used explicit direct attachment delivery and proactive projection sent the final block separately; this is expected compatibility behavior and is not counted as integrated Rich one-result evidence. Impact: release evidence now distinguishes the direct local tool path from the Telegram-turn queue orchestration that still requires a corrected follower smoke.
28
- - `Rich Replacement Fence`: Added exact agent-end barriers for an eligible single-artifact Rich final across session replacement: an old generation cannot begin the scheduled multipart mutation after replacement, and ownership loss while an already-started send settles prevents stale preview cleanup, fallback text/upload, queue dispatch, or other continuation effects. Impact: replacement sessions cannot inherit delayed Rich attachment work or trigger duplicate compatibility delivery after an in-flight result.
29
- - `Proactive Settings Copy`: Expanded the boolean detail card to describe both values consistently with other settings, placing `on` (default) first as the projection behavior and `off` second as the explicit opt-out that keeps local/autonomous public blocks in Pi without affecting normal Telegram-originated replies. Impact: operators can understand the behavioral difference before changing the toggle.
30
- - `Proactive Multi-Block Live Smoke`: After a full reload, an autonomous iteration emitted one public commentary checkpoint, ran an internal tool check, and emitted one final block. The operator confirmed both visible blocks arrived separately and in order in the assigned Telegram thread, while tool traffic produced no Telegram text and no duplicate appeared. Impact: normalized commentary-plus-final projection now has live client evidence in addition to deterministic direct/follower, privacy, ordering, and generation-fence coverage.
31
- - `Assistant Output Ownership`: Extended the existing Activity assistant-output path with proactive local/autonomous projection instead of introducing a separate proactive domain. Activity coordinates ordered admission, routing owns exact authority, outbound composes transformation and reply delivery, and `index.ts` retains direct live-port composition. Impact: intermediate proactive push remains a small extension of normalized assistant output rather than an artificial parallel state machine.
32
- - `Proactive Final-Mutation Fence`: Added an exact admission-authority guard at each proactive `sendMessage`, `sendRichMessage`, and edit transport mutation after asynchronous outbound text/button transformation and immediately before bus-aware direct/follower route selection. Added direct-epoch and follower-registration replacement barriers proving no mutation enters replacement transport. Impact: an outbound handler cannot create a stale-authority timing window between proactive queue admission checks and non-idempotent Bot API execution.
33
- - `Proactive Admission Authority`: Captured the authorized target, profile/token transport stamp, and exact direct leader epoch or follower registration generation with each admitted public block, then revalidated that immutable authority immediately before sending through the configured Rich/HTML reply runtime. Impact: delayed proactive work cannot drift across target changes, profile replacement, ownership loss/reacquisition, follower re-registration, or session replacement; known transport ambiguity retains the existing no-replay behavior.
34
- - `Proactive Follower Ordering`: Composed the proactive queue with the bus-aware API runtime under follower ownership and held the first routed Rich send behind a barrier. The second block did not enter follower IPC until the first completed, and both retained source order and target fields. Impact: proactive checkpoints cannot reorder when a follower routes them through the leader.
35
- - `Proactive Replacement Fence`: Added an extension-level barrier where an old session starts one proactive send, queues its final behind that send, shuts down, and starts a replacement session before the first acknowledgement returns. Only the already-started mutation executes; the queued old-generation final is dropped. Impact: session replacement cannot deliver delayed proactive blocks through the replacement runtime.
36
- - `Proactive Multi-Block Integration`: Added an extension-level local-run fixture with a completed checkpoint, tool boundary, hidden reasoning event, final block, and `agent_end`. It proves configured Rich delivery emits checkpoint then final exactly once and in order, excludes reasoning text, and does not revive the removed final-only path. Impact: the Grow-Loop-shaped output sequence now has deterministic end-to-end coverage without any runtime dependency on Grow Loop.
37
- - `Proactive Projection Wiring`: Connected normalized Activity `assistant-segment` events to Activity's assistant-output projection, rendered each admitted local/autonomous block through the configured Rich/HTML assistant sender and authorized instance target, started/stopped projection with the session generation, and removed Queue's old final-only `agent_end` sender. Impact: intermediate and final public blocks now share one ordered non-blocking path with no final duplication, while Telegram-owned turns and authority failures remain excluded.
38
- - `Assistant Output Projection`: Extended Activity's normalized output runtime to admit completed local/autonomous public assistant segments, deduplicate event identity, preserve source order, revalidate policy and delivery authority before each send, isolate failures, and drop queued work after generation stop. Impact: lifecycle wiring projects intermediate and final public blocks without a new domain, blocking Pi, or importing Grow Loop behavior; Telegram/unknown sources, empty text, disabled policy, and stale queued work remain silent.
39
- - `Proactive Output Boundary`: Confirmed Pi's `message_update.assistantMessageEvent` provides `text_end` as the completed public text boundary, distinct `thinking_*` and `toolcall_*` event families, and optional commentary/final phase signatures. Added an autonomous multi-block regression proving the existing Activity normalizer flushes completed text as intermediate/final assistant segments while reasoning and tool boundaries never enter segment text. Impact: proactive projection can consume normalized public blocks instead of token deltas or provider-private content without depending on Grow Loop.
40
- - `Integrated Rich Attachment Smoke`: After a full Pi reload, delivered one ordinary `telegram_attach` PNG plus final Markdown through the configured Rich assistant path in an assigned private topic. The operator confirmed one reply-anchored composite message with no duplicate attachment notice or second upload. Impact: the implemented direct-leader orchestration has live evidence beyond raw Bot API probing.
41
- - `Rich Thinking Safety`: Restricted the internal outgoing `InputRichMessage` contract to Markdown or HTML plus typed media, excluding explicit block arrays, and added an invariant that core reply, preview, queue, and attachment paths never construct Telegram Thinking blocks. Impact: draft-only Thinking placeholders cannot become an accidental transport for hidden reasoning; any future Activity use must start from explicitly public text.
42
- - `Rich Output Documentation`: Documented the single-artifact Rich result, exact ownership, reply/thread targeting, HTML/voice/Guest/multi-file compatibility boundaries, known-failure fallback, ambiguity no-replay rule, and remaining live follower evidence across outbound, architecture, multi-instance bus, public API, and docs navigation references. Impact: documented behavior now matches only probe-confirmed and deterministically tested implementation rather than broader Bot API possibility.
43
- - `Rich Attachment Delivery`: Integrated exactly one probe-confirmed PNG/JPEG photo, MP4 video, or MP3 audio artifact with final assistant Markdown into one configured-Rich multipart result. Successful sends record exact message ownership; known-safe rejection returns to the existing text-plus-attachment path, while `commit-unknown` and malformed successful results stop without fallback or replay. HTML mode, multiple/unsupported artifacts, Guest Mode, voice-only output, explicit voice markup, and OGG/Opus remain unchanged. Impact: requested supported media can arrive as one reply-anchored final result without weakening rendering choice, voice policy, generation checks, or ambiguity safety.
44
- - `Rich Attachment Planning`: Added an outbound-domain planner for exactly one probe-confirmed PNG/JPEG photo, MP4 video, or MP3 audio artifact in configured Rich mode. It constructs one target-scoped, reply-anchored multipart Rich Message with normalized Markdown and optional inline keyboard, while HTML mode, multiple artifacts, empty text, and OGG/Opus voice remain on existing paths. Impact: assistant integration can consume one deterministic request plan without changing voice policy or compatibility behavior.
45
- - `Follower Rich Upload Authorization`: Allowed target-scoped multipart `sendRichMessage` calls through the follower API boundary and added exact own-thread acceptance plus cross-thread rejection coverage. Impact: a future probe-confirmed Rich media upload can use the same direct/follower runtime without widening follower authority beyond its registered target.
46
- - `Rich Media Live Probe`: Confirmed Bot API and client behavior in an assigned private topic for one composite Rich Message containing HTTPS photo/video/audio, one multipart `attach://` voice note, and one cached-`file_id` voice note. The operator confirmed the composite media renders as one result and the independently requested voice notes remain separate messages; all three probe messages were then deleted successfully. Impact: URL, multipart, cached-file, media-classification, topic-targeting, and composite one-result behavior now have live evidence for bounded assistant-path integration.
47
- - `Structured Rich Markdown`: Added one Bot API 10.2 fixture that preserves a native table, inline and display mathematics, a preformatted code block, details, unordered and ordered lists, and quotation content through normalization and final `sendRichMessage` request construction. Impact: the established Markdown renderer already covers the targeted structured blocks without a parallel explicit-block renderer.
48
- - `Rich Media Request Probe`: Added deterministic direct and follower transport probes for Rich Message HTTPS photo references, cached voice-note `file_id`, and single-file `attach://` multipart request construction. Impact: JSON and multipart payload shapes now cross existing transport boundaries unchanged; Bot API acceptance and Telegram client rendering remain explicitly live-gated before automatic assistant integration.
49
- - `Rich Media Transport Types`: Added narrow Bot API 10.2 types for `InputRichMessage.media`, `InputRichMessageMedia`, photo/video/animation/audio media, and `InputMediaVoiceNote`, including native Rich Message body pass-through across direct and follower bus transports. Impact: URL or cached-file Rich media requests can retain their complete typed shape without exposing raw Bot API credentials or changing current rendering behavior; multipart upload remains evidence-gated open work.
50
- - `Telegram Bot API Reference`: Synchronized the vendored Telegram bot skill reference with Bot API 10.2 while preserving the established changelog, section, object-table, and method structure. Added complete Rich Message input blocks/media, voice-note media, Ephemeral Messages, Communities, subscription updates, and refreshed task-oriented line/anchor indexes. Impact: implementation and review can resolve current 10.2 fields and methods locally instead of relying on stale model knowledge.
33
+ - `Proactive Projection`: Extended the existing Activity assistant-output path to project deduplicated completed public `text_end` segments from local and autonomous work through the configured Rich/HTML sender. Intermediate and final blocks share one ordered, non-blocking queue; Telegram-owned turns, hidden reasoning, tool traffic, empty text, disabled policy, and the removed final-only `agent_end` path remain excluded. Impact: public checkpoints and finals arrive once and in source order without a parallel projection domain or a Grow Loop dependency.
34
+ - `Proactive Authority And Ordering`: Bound every admitted block to its target, profile/token transport stamp, session generation, and exact direct leader epoch or follower registration generation, then revalidated authority immediately before each non-idempotent send/edit mutation. Direct and follower barriers prove queued stale-generation work drops, follower IPC preserves order, replacement transport receives no old mutation, and ambiguous transport outcomes retain no-replay behavior. Impact: asynchronous handlers, ownership changes, follower re-registration, and session replacement cannot redirect or duplicate delayed projection.
35
+ - `Proactive Projection Evidence`: Deterministic local-run, direct/follower ordering, privacy, and replacement-fence coverage plus a post-reload live smoke confirmed that one public commentary checkpoint and final arrived separately and in order in the assigned thread while internal tool traffic stayed silent. Impact: the normalized multi-block lifecycle has both regression and client evidence without duplicate output.
36
+ - `Rich Attachment Results`: Added one deterministic outbound plan that combines exactly one probe-confirmed PNG/JPEG photo, MP4 video, or MP3 audio artifact with normalized final assistant Markdown, reply/thread targeting, and an optional inline keyboard in one configured-Rich multipart result. Known-safe rejection returns to text plus attachment; `commit-unknown` or malformed success stops without replay. HTML mode, multiple or unsupported artifacts, empty text, Guest Mode, voice-only output, explicit voice markup, and OGG/Opus retain their established paths. Impact: supported requested media can arrive as one reply-anchored result without weakening rendering choice, voice policy, or ambiguity safety.
37
+ - `Rich Authority And Safety`: Recorded exact message ownership, authorized follower multipart upload only to its registered thread, and fenced scheduled or in-flight Rich finals across session replacement and ownership loss. Old generations cannot start multipart mutation or continue with preview cleanup, fallback delivery, or queue dispatch after authority changes. The outgoing Rich contract permits Markdown/HTML plus typed media but excludes Thinking blocks, with invariants across reply, preview, queue, and attachment paths. Impact: replacement sessions, cross-thread followers, and hidden reasoning cannot redirect or duplicate Rich delivery.
38
+ - `Rich Transport And Rendering`: Added narrow Bot API 10.2 media and voice-note types plus direct/follower JSON, cached-file, HTTPS, and single-file `attach://` multipart transport coverage. Structured Markdown regressions preserve tables, inline/display mathematics, code, details, lists, and quotations through normalization and final request construction without a parallel block renderer. Impact: supported Rich content crosses both transport roles with its typed shape and native structure intact.
39
+ - `Rich Client Evidence`: Post-reload direct-leader and follower Telegram-turn smokes confirmed one queued PNG plus final Markdown arrives as one reply-anchored composite result with no duplicate upload, attachment notice, or final. A separate live probe confirmed HTTPS photo/video/audio composites plus multipart and cached-`file_id` voice notes, with independently requested voice notes remaining separate messages. Impact: the bounded integration has deterministic transport coverage and direct client evidence across leader and follower paths.
40
+ - `Rich Output Contracts And Reference`: Documented single-artifact behavior, ownership, targeting, compatibility fallbacks, ambiguity no-replay, and evidence boundaries across operator and maintainer docs. Synchronized the vendored Bot API reference with 10.2 Rich Message blocks/media, voice notes, Ephemeral Messages, Communities, subscription updates, and refreshed lookup indexes while preserving its full-reference structure. Impact: implementation and review can resolve current behavior and API fields locally without overstating broader Bot API possibilities.
51
41
 
52
42
  ## 0.22.1: Termux-Compatible Filesystem Transactions
53
43
 
@@ -56,23 +46,16 @@
56
46
 
57
47
  ## 0.22.0: Concurrency And Runtime Ownership Hardening
58
48
 
59
- - `Composition Root`: Reduced `index.ts` from 1,534 to 1,077 lines by moving transport generations, Threaded Mode orchestration, bus request identity, thread/status projections, exact message ownership, sync/provisioning state, session lifecycle sequencing, diagnostics persistence, Delivery policy, inbound target authority, follower forwarding, retry defaults, and leader identity state into their owning flat domains. The entrypoint now retains high-level runtime construction, narrow live-port wiring, and extension registration; direct one-to-one adapters remain only where another wrapper would hide rather than clarify composition. Impact: release-critical ownership and recovery policy can be tested beside its domain instead of accumulating in the extension bootstrap.
60
- - `Composition Validation`: Added domain regressions for every extracted state or policy runtime and retained structural guards that prohibit local runtime adapters, direct Node imports, dependency cycles, and leaf-domain drift in the entrypoint graph. The post-refactor release gate passes 1,229 tests with one platform-only skip, typecheck, strict Domain DAG, ABCd, invariants, package dry-run, audit with zero vulnerabilities, and `git diff --check`. Impact: the smaller composition root preserves the concurrency, lifecycle, routing, Delivery, and packaging guarantees established by the hardening release.
61
- - `Lock Transactions`: Serialized every `locks.json` acquisition, refresh, and release through one cross-process transaction while retaining atomic registry replacement. Serialized dead-owner recovery separately so concurrent recoverers cannot rename a newer live guard, and made transactional registry reads fail closed on read, parse, or shape errors. Added real child-process barriers for ordinary election, concurrent stale recovery, and parallel profile keys. Impact: simultaneous Pi processes can no longer both win an ordinary lock acquisition, corrupt recovery ownership, or erase unrelated profile locks through stale or invalid whole-registry writes.
62
- - `Lock Fencing`: Minted collision-resistant leader epochs independently from heartbeat time, retained the acquired profile key plus exact owner inside each runtime, and added process-global, time-monotonic same-process runtime generations for one-way reload handoff across cache-busted module reloads. Forced replacement must match the previously observed owner, only a newer same-process generation may supersede an older one, and dynamic profile resolution cannot reinterpret a retained token under another key. Refresh, release, direct Bot API authority, state persistence, and leader-epoch exposure now fail closed after ownership changes while accepted local queue work may continue through Pi. Impact: stale runtimes cannot reverse a replacement, mutate through its transport, refresh/delete its lock, or erase a colliding legacy profile owner.
63
- - `Follower Election`: Kept followers in disconnected/re-registration recovery when the leader IPC endpoint remains unavailable but the exact external leader lease stays live. Election now commits only if the observed owner remains stale or no owner has appeared, retries transfer across session replacement, losers re-register with the winner using their carried exact thread target, and binding promotion occurs only after acquisition. Impact: transient socket loss, refreshed leases, simultaneous followers, and session handoff can no longer create split-brain polling, strand a loser, or duplicate its Telegram thread.
64
- - `Leader Startup Lease`: Started exact-owner heartbeat refresh immediately after lock acquisition and kept it active across binding handoff, topic provisioning, server startup, and polling startup. Retained ownership refreshes synchronously before auto-start, ownership loss always performs a final post-start stop, and startup failure releases the acquired lock after cleanup. Impact: slow startup can no longer make a healthy acquired lease look stale or let a displaced generation finish as the active poller.
65
- - `Destructive Reconciliation`: Made missing or absent leader epochs fail closed whenever an ownership probe exists and revalidated the stamped epoch before each close/delete call, local deletion-state mutation, and cleanup persistence. Manual disconnect and previous-leader callers also stop all local teardown on exact `epoch undefined` ownership loss. Impact: leadership changes during Bot API waits cannot let an old leader delete a replacement-owned thread or continue stale cleanup.
66
- - `Provisioning And State Commit Fencing`: Added exact lock-transaction commits for thread snapshots and fenced topic provisioning before and after every persistence/Bot API boundary. Pending creation intents no longer expire into duplicate creation; a displaced provisioner records a returned topic only as transaction-serialized recovery evidence, and the next exact owner adopts that target without issuing another create. Impact: old leaders cannot publish authoritative bindings, while successful creates survive handoff without duplicate topics or permanent provisioning deadlock.
67
- - `Unix Endpoint Generations`: Bound each local bus server to a private generation socket and atomically published the stable profile endpoint as a relative symlink. Stop closes only the private generation path and leaves replacement links untouched; startup also waits for live legacy direct-socket servers before migration. Impact: delayed old-server teardown can no longer unlink or invalidate the replacement leader endpoint.
68
- - `Transactional Config`: Serialized `telegram.json` writers with recoverable cross-process transaction guards and merged each writer's recursive delta onto the latest disk snapshot. Polling persists the actual selected-profile snapshot, and same-profile `lastUpdateId` merges monotonically while concurrent profile and global-setting updates survive. Impact: independent Pi processes cannot regress accepted offsets, stale-replace sibling profiles, or erase unrelated shared configuration.
69
- - `Profile Handoff`: Preserved the selected profile identity across config reload, reordered profile activation so the old runtime stops before the new profile becomes visible, and stamped queue/final/Activity/Delivery work with immutable profile transport generations. Named-profile setup follows the same stop-old then commit-new order after validation. Impact: accepted same-profile work survives lock handoff, while old-profile work cannot use a replacement bot token or target.
70
- - `Session Generation Fence`: Added one session generation registry across lifecycle contexts and guarded agent/tool/message, compaction, preview, final-delivery, control-dispatch, and shutdown paths against replacement contexts. Preview operations invalidate by generation, stale scheduled finals drop before delivery, shutdown rechecks after polling/preview awaits, and preview cleanup has a bounded wait. Impact: delayed callbacks from an old Pi session cannot clear or deliver through its replacement session.
71
- - `Target And Ownership Scope`: Preserved immutable chat/thread targets on model-switch continuations, made public Delivery handles deeply frozen and privately bound, and scoped message ownership by bot profile plus exact follower registration generation. Forwarded traffic carries that generation, and follower edit/delete requires exact recorded message ownership. Impact: callers cannot retarget handles, and delayed or sibling follower traffic cannot mutate replacement-owned messages.
72
- - `Idempotent Inbound Admission`: Retained successfully handled update ids until their polling offset commits, so config-write retries advance offsets without re-running update side effects. Deferred media albums and split-text groups now retain their exact message sets through asynchronous admission failures, suspend across session replacement, rebind to the replacement context, and retry until dispatch succeeds. Impact: polling/config retries and transient queue failures no longer duplicate accepted prompts or silently lose grouped input.
73
- - `Method-Aware Bot API And Bus Retries`: Classified retry-safe Bot API methods separately from non-idempotent sends/uploads/topic creation. The local bus memoizes in-flight/completed request results, rejects request-id collisions, preserves structured ambiguity, and maps missing acknowledgements for non-idempotent follower calls to `TelegramApiCommitUnknownError`. Delivery exposes `commit-unknown` with any recoverable partial handle. Impact: response loss cannot silently authorize blind replay of messages, media, registrations, forwarded updates, or topic creation.
74
- - `Secondary Shared State`: Revision-serialized same-process thread snapshots, serialized JSONL append/reset/rotation across processes, restricted destructive log reset to the exact transport owner, identity-checked malformed-config quarantine under the config transaction, and replaced bare PPID follower identity with process-birth identity (`/proc` start ticks where available, collision-resistant generation fallback elsewhere). Impact: delayed writers and PID reuse cannot erase newer state or reinterpret stale follower ownership.
75
- - `Live Runtime Smoke`: After one clean process restart loaded the process-global generation fix, a local `/reload` retained the assigned leader thread, restored Telegram automatically, and accepted the next Telegram message without `/telegram-connect`, follower fallback, or takeover. A post-refactor private Guest Mode exchange then preserved `[telegram|guest:<remote-peer>]`, delivered a generated voice response, and delivered one requested text attachment through the one-result guest path. Impact: the stale self-lock belonged to the pre-fix runtime-generation collision rather than persistent state corruption, while the composition refactor preserves live thread, Guest identity, voice, and artifact delivery.
49
+ - `Composition Root And Validation`: Reduced `index.ts` from 1,534 to 1,077 lines by moving transport generations, Threaded Mode orchestration, request/ownership identity, synchronization, provisioning, lifecycle, diagnostics, Delivery policy, inbound authority, follower forwarding, and retry policy into their owning flat domains. Domain regressions and structural guards prohibit local runtime adapters, direct Node imports, dependency cycles, and leaf-domain drift. The release gate passed 1,229 tests with one platform-only skip, typecheck, strict Domain DAG, ABCd, invariants, package dry-run, zero-vulnerability audit, and `git diff --check`. Impact: the entrypoint retains high-level composition while release-critical policy remains independently testable and structurally enforced.
50
+ - `Lock Transactions And Fencing`: Serialized all `locks.json` acquisition, refresh, release, and dead-owner recovery through fail-closed cross-process transactions while preserving unrelated profile keys. Collision-resistant leader epochs, exact owner/profile retention, and time-monotonic same-process generations fence forced replacement, refresh, release, direct transport, state persistence, and reload handoff. Impact: concurrent or stale runtimes cannot both win ownership, reverse a replacement, mutate through replacement transport, or corrupt the shared registry.
51
+ - `Leader Startup And Follower Election`: Started exact-owner heartbeat refresh immediately after lock acquisition and retained it through binding handoff, provisioning, server startup, and polling; startup failure cleans up and releases ownership. Followers remain in re-registration recovery while an exact live lease exists, promote only after atomic stale/no-owner acquisition, and losers re-register with the winner using their carried target. Impact: slow startup, transient IPC loss, simultaneous followers, and session handoff cannot create split-brain polling, strand a follower, or duplicate its thread.
52
+ - `Reconciliation And Provisioning Fences`: Revalidated the stamped leader epoch before every destructive Bot API call, local deletion mutation, cleanup persistence, and provisioning boundary; missing ownership fails closed. Thread snapshots commit through exact lock transactions, pending creation intents survive displacement as serialized recovery evidence, and the next owner adopts successfully created targets without replaying creation. Impact: stale leaders cannot delete replacement-owned threads or publish authoritative bindings, while successful topic creation survives handoff without duplicates or deadlock.
53
+ - `Bus Endpoint Generations`: Bound each Unix bus server to a private generation socket and atomically published the stable profile endpoint as a relative symlink. Stop removes only its private path, replacement links remain intact, and startup waits for live legacy direct-socket servers before migration. Impact: delayed old-server teardown cannot unlink the replacement leader endpoint.
54
+ - `Configuration, Profiles, And Shared State`: Transactional `telegram.json` writers merge recursive deltas onto the latest snapshot, preserve monotonic profile offsets, and quarantine malformed config only behind identity-checked guards. Profile switching keeps the selected identity stable, stops old transport before exposing the new profile, and stamps queued/Activity/Delivery work with immutable profile generations. Revisioned thread snapshots, serialized JSONL rotation, owner-only destructive log reset, and process-birth follower identity protect secondary state. Impact: concurrent writers, delayed callbacks, profile replacement, and PID reuse cannot regress offsets, erase newer state, or reinterpret stale ownership.
55
+ - `Session, Target, And Message Ownership`: Added one generation registry across lifecycle, compaction, preview, final delivery, control dispatch, and shutdown so stale callbacks drop before touching replacement sessions. Model-switch continuations retain immutable targets; Delivery handles remain deeply frozen and privately bound; message ownership includes bot profile and exact follower registration generation. Impact: old sessions and sibling/replaced followers cannot retarget work, clear replacement state, or edit/delete messages they no longer own.
56
+ - `Idempotent Inbound Admission`: Retained handled update ids until polling-offset commit and preserved exact deferred album/split-text message sets across admission failure and session replacement. Suspended groups rebind to the replacement context and retry until dispatch succeeds. Impact: config retries and transient queue failures do not duplicate accepted prompts or silently lose grouped input.
57
+ - `Method-Aware Retry Safety`: Classified retry-safe Bot API methods separately from non-idempotent sends, uploads, and topic creation. The local bus memoizes request results, rejects id collisions, preserves ambiguity, maps missing non-idempotent acknowledgements to `TelegramApiCommitUnknownError`, and exposes `commit-unknown` with recoverable partial handles. Impact: response loss cannot authorize blind replay of messages, media, registrations, forwarded updates, or topic creation.
58
+ - `Live Runtime Evidence`: After a clean restart, local `/reload` retained the assigned leader thread, restored Telegram automatically, and accepted the next message without reconnect, follower fallback, or takeover. A private Guest Mode exchange preserved guest attribution and delivered generated voice plus a requested attachment through the one-result path. Impact: live evidence confirmed generation recovery, thread identity, Guest routing, voice, and artifact delivery after the hardening refactor.
76
59
 
77
60
  ## 0.21.1: Runtime And Session Semantics Hotfix
78
61
 
package/README.md CHANGED
@@ -92,7 +92,7 @@ The first Telegram user to message the bot becomes the allowed owner. Other user
92
92
 
93
93
  | Surface | What you can do | Why it matters |
94
94
  | --- | --- | --- |
95
- | Prompt intake | Send text, replies, edits, images, files, albums, voice notes, and handler output into Pi. | Telegram becomes a real mobile input surface with file/context references, not just a text tunnel. |
95
+ | Prompt intake | Send text, replies, edits, images, files, albums, voice notes, forwards with adjacent comments, and handler output into Pi. | Telegram becomes a real mobile input surface; one forward-plus-comment gesture stays one attributed prompt even for photo-only forwards. |
96
96
  | 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. |
97
97
  | 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. |
98
98
  | 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. |
@@ -104,7 +104,7 @@ The first Telegram user to message the bot becomes the allowed owner. Other user
104
104
  | 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. |
105
105
  | Outbound artifacts | Return generated files through `telegram_attach` during active turns or explicit direct delivery. | Agents send real artifacts as files, not pasted blobs. |
106
106
  | Voice input | Route audio through configured command-template handlers, programmatic handlers, or STT providers. | Voice notes become usable prompt context. |
107
- | Voice output | Use `telegram_voice`, reply modes, configured voice handlers, and TTS providers. | Replies can become Telegram voice messages when the workflow calls for it. |
107
+ | Voice output | Choose `hidden`, `mirror`, or `always`; active automatic turns carry one compact `[voice] delivery: automatic voice` line, while explicit `telegram_voice` remains available. | Voice policy stays dynamic and model-legible without duplicating the full action contract in every prompt. |
108
108
  | Buttons | Turn top-level `telegram_button` comments into inline buttons. | Assistant-authored choices become native Telegram interactions. |
109
109
  | Callback routing | Route known callbacks to the owner extension and unknown callbacks back into Pi. | Companion extensions can build UI without polling Telegram themselves. |
110
110
  | Threaded Mode | Run one leader plus visible follower Pi instances through named private-chat threads. | One bot can host a local multi-instance Pi organism without hidden process spawning. |
@@ -165,7 +165,7 @@ Named profile identifiers contain only lowercase ASCII letters and digits (maxim
165
165
 
166
166
  ### Queue Runtime
167
167
 
168
- Messages sent while Pi is busy become queued turns. Priority lanes support control actions and model-switch continuations. Queue controls let you inspect, delete, promote, and dispatch work from Telegram without touching the terminal.
168
+ Messages sent while Pi is busy become queued turns. Priority lanes support control actions and model-switch continuations. Queue controls let you inspect, delete, promote, and dispatch work from Telegram without touching the terminal. If Pi automatically retries a transient provider failure, the active Telegram turn stays bound until the successful reply arrives or Pi confirms that the run has settled.
169
169
 
170
170
  ### Native Rich Markdown
171
171
 
@@ -126,7 +126,6 @@ Telegram configuration lives in `~/.pi/agent/telegram.json`. Polling ownership l
126
126
  - Live polling owners require explicit takeover confirmation.
127
127
  - Long-lived polling timers use snapshotted ownership context and stop local polling when the lock no longer points at their own process.
128
128
  - `locks.json` owns only external Telegram control/polling. Local extension and accepted queue state remain per Pi instance when ownership moves, but previews, final delivery, dispatch transport mutations, and other delayed work stop until exact direct or follower authority becomes valid again; ownership loss never permits delivery through replacement transport.
129
- - Proactive local/autonomous public-output projection is not accepted-turn delivery. It is allowed only when `assistant.proactivePush` is enabled and this instance has exact direct ownership or an authenticated follower registration. Every completed public block, including visible commentary and the final block, retains its admission-time target and transport authority; hidden reasoning, tools, token deltas, stale work, and Telegram-owned turns are excluded.
130
129
 
131
130
  Deleting `locks.json` resets runtime ownership without deleting Telegram configuration.
132
131
 
@@ -136,7 +135,7 @@ Telegram private-chat Threaded Mode is the public switch for multi-instance Tele
136
135
 
137
136
  Named Telegram profiles are orthogonal to Threaded Mode. The selected profile chooses the bot/session slice (`botToken`, `botId`, `botUsername`, `allowedUserId`, `lastUpdateId`) and scopes singleton locks, diagnostics logs, state files, thread/bus owner keys, and leader/follower IPC endpoints; it must not change the Threaded Mode rules. Within one selected profile, leader/follower election, bus transport, thread provisioning, routing, ownership forwarding, cleanup, and runtime diagnostics behave exactly as they do for the default profile. A different selected profile is a parallel bot runtime: its locks, `tmp/telegram/state.<profile>.json`, `tmp/telegram/logs.<profile>.jsonl`, `tmp/telegram/logs.<profile>._prev.jsonl`, thread bindings, Unix sockets, and Windows named pipes are isolated from the default profile and from other named profiles while shared bridge settings remain top-level/global. The default profile preserves legacy state, log, socket, and named-pipe paths for compatibility.
138
137
 
139
- Profile reality follows three explicit storage classes. `telegram.json` shared settings and extension registries are process-global platform configuration; profile bot/session fields and observable transport/routing authority are profile-scoped; queues, active turns, ownership caches, menu state, and runtime controllers are session-local memory. Config persistence serializes cross-process writers and applies each recursive mutation delta to the latest disk snapshot, so named-profile offsets and unrelated global/profile updates do not stale-replace one another. Runtime profile switching follows stop-old/commit-new ordering: reload keeps the selected identity stable, old polling/lock/bus teardown finishes while all dynamic resolvers still point at the old profile, and only then may activation expose the new token, lock key, state path, target namespace, and IPC endpoint. Downloaded attachments use UUID-prefixed names in the shared Telegram scratch directory and are session artifacts rather than identity or routing authority, so cross-profile cleanup is limited to stale scratch files and cannot redirect live traffic.
138
+ Profile reality follows three explicit storage classes. `telegram.json` shared settings and extension registries are process-global platform configuration; profile bot/session fields and observable transport/routing authority are profile-scoped; queues, active turns, ownership caches, menu state, and runtime controllers are session-local memory. Config persistence serializes cross-process writers and applies each recursive mutation delta to the latest disk snapshot, so named-profile offsets and unrelated global/profile updates do not stale-replace one another. Polling publishes only its monotonic `lastUpdateId` into the current config-store snapshot; it never persists the detached full config object captured at poll start, which would interpret newer Settings fields as deletions. Runtime profile switching follows stop-old/commit-new ordering: reload keeps the selected identity stable, old polling/lock/bus teardown finishes while all dynamic resolvers still point at the old profile, and only then may activation expose the new token, lock key, state path, target namespace, and IPC endpoint. Downloaded attachments use UUID-prefixed names in the shared Telegram scratch directory and are session artifacts rather than identity or routing authority, so cross-profile cleanup is limited to stale scratch files and cannot redirect live traffic.
140
139
 
141
140
  When Threaded Mode is active, the current polling owner is also the Telegram bus leader. The leader owns the local bus endpoint (Unix-domain socket on Unix-like platforms, named pipe on native Windows), polls `getUpdates`, performs direct Bot API calls, records follower heartbeats, prunes stale followers, and provisions Telegram UI thread targets through live runtime/bus state. Follower liveness is intentionally fast because heartbeat traffic is local IPC: followers heartbeat every `1s`, the leader treats them as stale after `2s`, and the prune loop runs every `1s` so stopped followers are detected promptly while active forwarded updates/API calls still refresh liveness. Heartbeat pruning is silent liveness bookkeeping: it preserves the follower thread binding and does not send a Telegram-visible disconnected notice, because the common cause may be leader reload or IPC handoff rather than a dead follower. Successful follower target reuse refreshes the binding's recovery timestamp. Absent follower bindings remain durable restoration hints until explicit stale, deleted, offline, or reconciliation evidence invalidates them; process absence and heartbeat pruning alone do not remove them. If an authenticated live follower carries an exact target that is absent from current bindings, the leader recovers it only behind a synchronous visibility probe: success activates it, explicit stale evidence provisions a replacement, and ambiguous failure rejects registration. A carried slot is restored only when it is not already occupied. `tmp/telegram/logs.jsonl` is a session-local redacted runtime evidence stream for race debugging; it resets on extension start and runtime scope changes, and must not become routing/provisioning authority. `tmp/telegram/state.json` is an extension+bot observable/debug snapshot aligned with status diagnostics: `source: "snapshot"` and `writtenAtMs` mark it as observational, not authoritative. All instances on one Telegram profile read the same snapshot, but only the active transport lock owner persists it; followers become writers only after promotion. Status-only persistence reloads current disk bindings before serialization, preventing a stale follower/status view from erasing newer leader-owned targets. Fresh capability observations may skip redundant startup probes, but stale snapshots re-probe before suppressing bus/thread behavior. Top-level `bot` mirrors bot-wide capabilities such as thread mode, `runtime` describes process role/status, `liveRoster` mirrors followers/current targets/reservations, `diagnostics` mirrors recent status/debug signals including the latest thread-reconciler phase/counts, `threads` stores current routeable bindings, TTL-bounded reservations explain short-lived slot collision guards, and TTL-pruned `pendingProvisions` protects in-flight topic creation slots from cleanup/allocation races. Fresh provisioning writes pending state before the Bot API create call, adds the returned target to the pending record, persists a `starting` binding, then promotes it to `active` and clears pending state. If final binding persistence fails after Telegram returns a thread id, the targeted pending provision remains as cleanup/retry evidence. Once targeted pending provisions expire, they are retained for `thread-reconciler` close/delete cleanup and pending scratchpad removal after a successful cleanup apply; untargeted expired pending records can prune without cleanup because no Telegram thread id exists. Runtime events coalesce status-snapshot writes so transient bus/API/update failures remain inspectable even when the operator has not opened `/telegram-status`. The bridge must not keep a durable `telegram-targets.json` target history; stale/offline/failed thread observations are pruned instead of reused. Previous-process leader bindings that still probe alive become reservations/collision guards, not routeable active threads, so a reloaded leader can take the next free slot without duplicating the same visible tab name. The thread chat is always the private bot DM with the paired owner (`allowedUserId`). In Telegram private-chat Threaded Mode, the leader creates/reuses its own thread before polling — it is a real bound instance, not a dispatcher. Followers authenticate bus envelopes with the leader-minted capability secret stored in the active lock entry. Leader lock entries also carry a stable `leaderEpoch` minted on acquisition and preserved across heartbeat refreshes; leader-owned cleanup/provisioning plans stamp that epoch, and Thread Reconciler apply skips destructive work if leadership has moved on before side effects run. Followers own their own Pi session state, queue, active turns, previews, menus, and lifecycle hooks, but route allowlisted, target-scoped Telegram API calls through the leader. When a follower promotes after heartbeat loss, status/state diagnostics expose only the transient `electing` lifecycle phase; stable `leader`/`follower` identity stays in the bus role so diagnostics do not duplicate role state. The TUI status bar and `/telegram-status` report `leader` or `follower` role so a registered follower is not shown as generically disconnected. Terminal status identity and the `[telegram|thread:name]` prompt label use the same target-aware current-instance resolver: registered local metadata wins over a stale shared binding for the matching target, while the binding remains a fallback for partial metadata.
142
141
 
@@ -146,7 +145,7 @@ Follower binding is manual and process-first: the operator starts another Pi pro
146
145
 
147
146
  When Threaded Mode is enabled, writing a message in the `All` tab can create a new thread without an existing instance binding. The bridge detects this during update execution: if a message from the owner has a `message_thread_id` that no instance owns, the message is routed to the unbound-thread handler instead of the leader's normal message handler. In the default runtime, this handler first reclaims the thread for the leader when the leader has no active bound thread, assigns the current leader thread identity, persists the active binding, and serves the prompt locally. If the leader already has an active thread, the handler preserves the prompt in the source Telegram thread and shows a target-thread chooser; explicit successful routing may later close/delete only extra confirmed source threads through `thread-reconciler` proof-before-delete planning and stale-epoch fencing. Unknown `forum_topic_created` service events are recorded as observations and are not destructive cleanup proof, because Telegram can deliver creation events before local provisioning/binding writes become visible across reloads. If Threaded Mode is unavailable, the message is processed normally through classic routing.
148
147
 
149
- Threadless messages from `All` are not routed as prompts once bound threads exist, because `All` cannot identify the owning Pi instance. Known commands from `All` open a compact live-target chooser, while ordinary threadless prompts get guidance to use a bound Pi thread tab. This preserves a safe default after the operator closes every thread while still preventing later accidental empty tabs from black-holing prompts or spawning hidden Pi processes. The operator-facing path for another instance is visible manual follower registration: start Pi in a terminal, then run `/telegram-connect`.
148
+ Threadless messages from `All` are not routed as prompts once bound threads exist, because `All` cannot identify the owning Pi instance. Known commands open a compact live-target chooser, while ordinary prompts get guidance to use a bound Pi thread. This prevents accidental empty tabs from black-holing prompts or bypassing the manual follower-registration contract above.
150
149
 
151
150
  The routing identity split is deliberate:
152
151
 
@@ -168,7 +167,7 @@ All inbound updates are gated by the configured authorized user id.
168
167
  2. Persist update offsets only after successful handling; repeated handler failures are bounded.
169
168
  3. Filter to the paired private user; guest-mode updates require an existing paired user and cannot establish first pairing.
170
169
  4. Dispatch owned callbacks and controls before fallback prompt forwarding.
171
- 5. Coalesce media groups, likely split long text, and a short human comment followed by an adjacent forwarded message when needed.
170
+ 5. Coalesce media groups, likely split long text, and one adjacent forward-plus-comment pair in either order when needed.
172
171
  6. Download files into `~/.pi/agent/tmp/telegram` with size limits and partial-download cleanup.
173
172
  7. Run configured/programmatic inbound handlers in order, appending successful stdout under `[outputs]`.
174
173
  8. Add local attachments under `[attachments]`, optional voice context, and optional final `[time]` context.
@@ -176,7 +175,7 @@ All inbound updates are gated by the configured authorized user id.
176
175
  10. Handle `edited_message` updates separately while the original turn is still queued.
177
176
  11. Dispatch only when all safety gates are clear.
178
177
 
179
- Long-text split recovery remains conservative: only human text at or above the near-limit threshold opens its debounce window. A separate bounded one-second comment window applies to ordinary short human text so a forwarded message arriving in the next polling response can join the same Pi turn; an adjacent matching forward flushes immediately. Commands, bots, captions, media groups, different senders/targets, reversed ids, and distant message ids do not enter this pairing path.
178
+ Long-text split recovery remains conservative: only human text at or above the near-limit threshold opens its debounce window. Forward annotation has two semantic layers: the forward owns its source text/caption/media, while an optional separate owner-authored annotation normally precedes it. A bounded one-second pairing window joins that annotation and adjacent forward in either transport order, including a media-only forward without source caption text; the matching opposite-kind message flushes immediately. Same-kind rapid messages, commands, bots, ordinary non-forward captions, media groups, different senders/targets, reversed ids, and distant message ids do not enter this pairing path. Prompt construction always places the owner annotation first, followed by `[forward|from:...]` with the forward's own source text/caption, then source-attributed forwarded attachments, regardless of arrival order.
180
179
 
181
180
  ### Queue And Dispatch Safety
182
181
 
@@ -201,7 +200,7 @@ Dispatch requires:
201
200
  - `ctx.isIdle()` is true.
202
201
  - `ctx.hasPendingMessages()` is false.
203
202
 
204
- A dispatched prompt remains queued until `agent_start` consumes it. This keeps the active Telegram turn bound for previews, attachments, aborts, and final replies.
203
+ A dispatched prompt remains queued until `agent_start` consumes it. This keeps the active Telegram turn bound for previews, attachments, aborts, and final replies. A low-level `agent_end` error also retains that active turn because Pi may retry automatically; a later successful `agent_end` delivers through the original target and metadata, while `agent_settled` proves that an unrecovered error can be finalized once before queue dispatch resumes.
205
204
 
206
205
  Post-agent-end queue dispatch uses a session-bound deferred dispatcher. It is activated on session start, clears timers on shutdown, and skips callbacks from older generations before touching `ExtensionContext`. Dispatch stays session-bound after polling ownership moves elsewhere. When a queued Telegram prompt is forwarded into Pi, it uses a normal `sendUserMessage(content)` turn after the bridge's idle/dispatch guards pass; it does not use Pi's `followUp` delivery option or inject terminal input.
207
206
 
@@ -300,11 +299,11 @@ Status rendering distinguishes connected, active, dispatching, queued, tool-runn
300
299
 
301
300
  Queue reactions are shortcut controls for waiting turns. Promotion reactions (`👍`, `⚡️`, `❤️`, `🕊`, `🔥`) move prompts to priority; removal reactions (`👎`, `👻`, `💔`, `💩`, `🗑`) remove waiting turns because ordinary Telegram DM deletions are not exposed through Bot API polling.
302
301
 
303
- `/telegram-status` records grouped diagnostics for transport/API, polling/update, prompt dispatch, controls, typing, compaction, setup, session lifecycle, attachment queue/delivery, and recent redacted runtime events. Expected preview noise such as unchanged edit responses is filtered out.
302
+ `/telegram-status` records grouped diagnostics for transport/API, polling/update, prompt dispatch, controls, typing, compaction, setup, session lifecycle, attachment queue/delivery, and recent redacted runtime events. Expected preview noise such as unchanged edit responses is filtered out. The compact TUI status renders only `error`; detailed failure text remains in diagnostics and profile-scoped logs instead of expanding the status line.
304
303
 
305
304
  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. Hidden reasoning, tool traffic, token deltas, local prompt text, Telegram-owned turns, and stale generations are not mirrored. 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.
306
305
 
307
- Telegram prompt guidance is context-aware. Unconfigured sessions receive no bridge suffix. Local/TUI prompts receive only explicit direct-delivery guidance so ordinary terminal replies do not learn raw Telegram action-comment syntax. Telegram-originated turns receive the full inbound context, phone-width output, and native action contract, including the 37-display-cell mobile readability hint.
306
+ Telegram prompt guidance is context-aware. Unconfigured sessions receive no bridge suffix. Local/TUI prompts receive only explicit direct-delivery guidance. 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.
308
307
 
309
308
  ## In-Flight Model Switching
310
309
 
@@ -374,7 +374,7 @@ All files containing routing, chat ids, thread ids, or process details use priva
374
374
  - A missed heartbeat does not delete, close, mark offline, or send a disconnected notice for the follower's Telegram thread binding because the common cause may be leader reload, IPC handoff, or transient reconnect rather than a dead follower.
375
375
  - Followers treat rejected/missing heartbeat acknowledgements as registration loss: retain the last known target locally, clear registered truth, try to re-register with the current leader, wait a short leader-reload grace window, and retry. They promote only after the exact leader lease becomes stale or inactive; a live owner with an unreachable endpoint leaves the follower disconnected/retrying rather than creating a competing poller.
376
376
  - Persisted current manual-follower bindings survive process absence as restoration hints, but cannot receive inbound or outbound work until the replacement follower authenticates and registers with a fresh generation.
377
- - Freshly provisioned follower registration sends a compact connected notice in the assigned thread. Cross-session restoration sends the same notice once as a visibility probe: success surfaces the reused tab, explicit stale-topic rejection provisions a monotonic replacement before registration succeeds, and ambiguous/non-stale failure records diagnostics and rejects registration without replay or speculative replacement.
377
+ - Fresh registration sends a compact connected notice in the assigned thread; cross-session restoration uses that same notice as the visibility probe and follows the stale/ambiguous recovery contract defined above.
378
378
  - Registration requires a present generation, and explicit disconnect requires that same exact live generation. Leader-side registration and disconnect mutations serialize per durable follower profile across old and replacement runtime instance IDs, so a replacement registration cannot overtake awaited destructive cleanup and an old disconnect cannot remove its successor's routing authority.
379
379
  - Successful forwarded updates and follower-originated API calls refresh liveness, so active followers are not pruned only because the interval heartbeat tick lagged.
380
380
  - Destructive follower thread teardown belongs to explicit `/telegram-disconnect` or confirmed reconciliation actions, not generic heartbeat pruning. A registered follower sends an authenticated request fenced by its exact registration generation; the active leader closes and deletes that follower's exact topic, marks its durable binding offline, removes live routing authority, and acknowledges completion before the follower stops. Cleanup counts as confirmed only after successful deletion or explicit already-gone evidence. Incomplete cleanup preserves the binding and registration for retry. A promoted leader uses its current owned leader epoch even when the inherited record still carries a historical `manual-follower` owner label.