@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 +19 -12
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +42 -59
- package/README.md +3 -3
- package/docs/architecture.md +7 -8
- package/docs/multi-instance-bus.md +1 -1
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +2 -2
- package/docs/ui-style.md +1 -1
- package/docs/voice.md +8 -9
- package/index.ts +8 -7
- package/lib/bindings.ts +15 -16
- package/lib/bus-leader.ts +11 -128
- package/lib/config.ts +32 -14
- package/lib/menu-settings.ts +37 -97
- package/lib/prompts.ts +4 -2
- package/lib/queue.ts +52 -8
- package/lib/routing.ts +10 -16
- package/lib/status.ts +1 -1
- package/lib/text-groups.ts +29 -15
- package/lib/thread-reconciler.ts +3 -71
- package/lib/threads.ts +0 -43
- package/lib/turns.ts +25 -21
- package/lib/voice.ts +6 -7
- package/package.json +3 -2
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
|
|
89
|
-
-
|
|
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-
|
|
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
|
|
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|
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
6
|
-
- `Explicit Thread Disconnect`:
|
|
7
|
-
- `
|
|
8
|
-
- `
|
|
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
|
-
- `
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
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
|
|
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
|
-
- `
|
|
28
|
-
- `
|
|
29
|
-
- `Proactive
|
|
30
|
-
- `
|
|
31
|
-
- `
|
|
32
|
-
- `
|
|
33
|
-
- `
|
|
34
|
-
- `
|
|
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,
|
|
60
|
-
- `
|
|
61
|
-
- `
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
65
|
-
- `
|
|
66
|
-
- `
|
|
67
|
-
- `
|
|
68
|
-
- `
|
|
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
|
|
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 |
|
|
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
|
|
package/docs/architecture.md
CHANGED
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
-
|
|
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.
|