@llblab/pi-telegram 0.17.5 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/AGENTS.md +67 -32
  2. package/BACKLOG.md +59 -19
  3. package/CHANGELOG.md +36 -15
  4. package/README.md +63 -35
  5. package/docs/README.md +3 -1
  6. package/docs/architecture.md +55 -23
  7. package/docs/callback-namespaces.md +1 -1
  8. package/docs/inbound.md +1 -1
  9. package/docs/locks.md +0 -2
  10. package/docs/multi-instance-bus.md +483 -0
  11. package/docs/outbound.md +4 -3
  12. package/docs/public-api.md +12 -10
  13. package/docs/sections.md +2 -2
  14. package/docs/ui-style.md +76 -0
  15. package/index.ts +789 -32
  16. package/lib/bindings.ts +68 -12
  17. package/lib/bus-api.ts +314 -0
  18. package/lib/bus-follower.ts +853 -0
  19. package/lib/bus-leader.ts +915 -0
  20. package/lib/bus.ts +866 -0
  21. package/lib/command-templates.ts +9 -11
  22. package/lib/commands.ts +133 -47
  23. package/lib/config.ts +53 -5
  24. package/lib/lifecycle.ts +23 -7
  25. package/lib/locks.ts +230 -66
  26. package/lib/media.ts +30 -2
  27. package/lib/menu-model.ts +48 -17
  28. package/lib/menu-queue.ts +51 -20
  29. package/lib/menu-settings.ts +9 -5
  30. package/lib/menu-status.ts +3 -0
  31. package/lib/menu-thinking.ts +3 -0
  32. package/lib/menu.ts +67 -26
  33. package/lib/outbound-attachments.ts +102 -17
  34. package/lib/outbound-buttons.ts +6 -2
  35. package/lib/outbound-voice.ts +31 -11
  36. package/lib/outbound.ts +6 -4
  37. package/lib/ownership.ts +119 -0
  38. package/lib/pi.ts +26 -3
  39. package/lib/polling.ts +477 -7
  40. package/lib/preview.ts +141 -88
  41. package/lib/prompt-templates.ts +3 -3
  42. package/lib/prompts.ts +80 -30
  43. package/lib/queue.ts +193 -91
  44. package/lib/rendering.ts +0 -25
  45. package/lib/replies.ts +187 -55
  46. package/lib/routing.ts +1673 -9
  47. package/lib/runtime-log.ts +123 -0
  48. package/lib/runtime.ts +84 -12
  49. package/lib/sections.ts +28 -21
  50. package/lib/setup.ts +1 -1
  51. package/lib/status.ts +532 -9
  52. package/lib/sync.ts +618 -0
  53. package/lib/target.ts +49 -0
  54. package/lib/telegram-api.ts +405 -40
  55. package/lib/text-groups.ts +5 -1
  56. package/lib/thread-reconciler.ts +915 -0
  57. package/lib/threads.ts +2205 -0
  58. package/lib/turns.ts +48 -3
  59. package/lib/updates.ts +355 -32
  60. package/package.json +24 -2
  61. package/docs/telegram-bot-api-rich-messages.md +0 -890
package/AGENTS.md CHANGED
@@ -4,25 +4,26 @@
4
4
 
5
5
  - `Constraint-Driven Evolution`: Add structure when the bridge gains real operator or runtime constraints
6
6
  - `Single Source of Truth`: Keep durable rules in `AGENTS.md`, open work in `BACKLOG.md`, completed delivery in `CHANGELOG.md`, and deeper technical detail in `/docs`
7
- - `Boundary Clarity`: Separate Telegram transport concerns, π integration concerns, rendering behavior, and release/documentation state
7
+ - `Boundary Clarity`: Separate Telegram transport concerns, Pi integration concerns, rendering behavior, and release/documentation state
8
8
  - `Progressive Enhancement + Graceful Degradation`: Prefer behavior that upgrades automatically when richer runtime context exists, but always preserves a useful fallback path when it does not
9
- - `Runtime Safety`: Prefer queue and rendering behavior that fails predictably over clever behavior that can desynchronize the Telegram bridge from π session state
9
+ - `Runtime Safety`: Prefer queue and rendering behavior that fails predictably over clever behavior that can desynchronize the Telegram bridge from Pi session state
10
10
  - `Mobile Companion Boundary`: `pi-telegram` extends a live Pi session for phone use; it is not a remote terminal, PTY supervisor, or process launcher. Do not add terminal-control tricks to make Telegram pretend to be the TUI.
11
- - `Pi-Native Extensibility`: `pi-telegram` should inherit π's own extension philosophy. It is not only a Telegram adapter; it should become a small, convenient, composable Telegram shell for π extensions, where new capabilities plug into stable contracts instead of forking polling, transport, or menu ownership.
11
+ - `Pi-Native Extensibility`: `pi-telegram` should inherit Pi's own extension philosophy. It is not only a Telegram adapter; it should become a small, convenient, composable Telegram shell for Pi extensions, where new capabilities plug into stable contracts instead of forking polling, transport, or menu ownership.
12
+ - `Bidirectional Binding`: Treat instance↔thread and bot↔Telegram-client state as two-way relationships, not one-way delivery addresses. The bridge should actively create, observe, repair, and reflect bindings so responsiveness, recognizability, and controllability remain high across both Pi instance state and Telegram client surfaces.
12
13
 
13
14
  ## 1. Concept
14
15
 
15
- `pi-telegram` is a Telegram runtime adapter for π: a session-local operator console that turns a private Telegram DM into a runtime surface for prompt intake, streaming previews, queue management, model/thinking/settings controls, inbound/outbound handler pipelines, voice/buttons, artifacts, and extension callback interop. Treat it as a Telegram membrane around π, not a narrow message pipe.
16
+ `pi-telegram` is a Telegram runtime adapter for Pi: a session-local operator console that turns a private Telegram DM into a runtime surface for prompt intake, streaming previews, queue management, model/thinking/settings controls, inbound/outbound handler pipelines, voice/buttons, artifacts, and extension callback interop. Treat it as a Telegram membrane around Pi, not a narrow message pipe.
16
17
 
17
18
  The core product loop is mobile continuation: start or supervise work in the terminal, then continue from Telegram while away from the keyboard. Telegram controls should be a safe extension-facing subset of the live session, not a replacement for Pi's interactive TUI.
18
19
 
19
20
  ## 2. Identity & Naming Contract
20
21
 
21
- - `Telegram turn`: One unit of Telegram input processed by π; this may represent one message or a coalesced media group
22
- - `Queued Telegram turn`: A Telegram turn accepted by the bridge but not yet active in π
23
- - `Active Telegram turn`: The Telegram turn currently bound to the running π agent loop
22
+ - `Telegram turn`: One unit of Telegram input processed by Pi; this may represent one message or a coalesced media group
23
+ - `Queued Telegram turn`: A Telegram turn accepted by the bridge but not yet active in Pi
24
+ - `Active Telegram turn`: The Telegram turn currently bound to the running Pi agent loop
24
25
  - `Preview`: The transient streamed response shown through Telegram drafts or editable messages before the final reply lands
25
- - `Scoped models`: The subset of models exposed to Telegram model selection when π settings or CLI flags limit the available list
26
+ - `Scoped models`: The subset of models exposed to Telegram model selection when Pi settings or CLI flags limit the available list
26
27
 
27
28
  ## 3. Project Topology
28
29
 
@@ -33,10 +34,13 @@ The core product loop is mobile continuation: start or supervise work in the ter
33
34
  - `/docs/README.md`: Documentation index for technical project docs
34
35
  - `/docs/architecture.md`: Runtime and subsystem overview for the bridge
35
36
  - `/docs/public-api.md`: Stable public API map for commands, config, assistant markup, extension APIs, package entrypoints, and compatibility boundaries
36
- - `/README.md`: User-facing project entry point. Keep its rhythm as install connect use core features docs, with vivid examples that explain the runtime adapter/operator-console model without duplicating full docs.
37
+ - `/.agents/skills/`: Project-local agent skills that support this repository. Keep each skill's durable operating guidance in its own `SKILL.md`; use `AGENTS.md` only for topology and maintenance rules.
38
+ - `telegram-bot`: Agent-facing Telegram Bot API lookup skill. Keep the vendored `api.md` reference intact and put navigation, freshness, line-range, task, risk, and synonym indexes in `SKILL.md`.
39
+ - `domain-dag`: Agent-facing architecture skill and validator for this repository's flat Domain DAG, composition-root, header, cycle, and shared-bucket invariants.
40
+ - `/README.md`: User-facing project entry point and product hyperindex. It is the public representation of the extension, not agent-maintenance context. Keep its rhythm as identity → mental model/lenses → install → connect → use → core features → docs, with vivid examples that explain the runtime adapter/operator-console model without duplicating full docs. It should describe the extension through several durable lenses — operator companion, Telegram UI harness, Pi runtime adapter, multi-instance/thread organism, companion-extension platform, delivery/media surface, safety boundary, and release maturity — so a reader can understand what the extension can become without wading into minor implementation detail.
37
41
  - `/AGENTS.md`: Durable engineering and runtime conventions
38
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.
39
- - `/CHANGELOG.md`: Completed delivery history. Prefer multiple domain-scoped bullets in the form `[Domain]`: change + impact instead of accumulating unrelated changes into one long entry.
43
+ - `/CHANGELOG.md`: Completed delivery history focused on the final released behavior and user/operator/developer impact. Prefer multiple domain-scoped bullets in the form `[Domain]`: change + impact instead of accumulating unrelated changes into one long entry. 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.
40
44
 
41
45
  ## 4. Core Entities
42
46
 
@@ -44,6 +48,12 @@ The core product loop is mobile continuation: start or supervise work in the ter
44
48
  - `PendingTelegramTurn`: Queue-domain prompt turn state for queued and active Telegram-originated work
45
49
  - `TelegramPreviewRuntimeState`: Preview-domain streaming state for drafts or editable Telegram messages
46
50
  - `TelegramModelMenuState`: Shared inline application-menu state for status, model, thinking, and queue menu messages
51
+ - `TelegramTarget`: Transport destination shape `{ chatId, threadId? }`, where classic private chats omit `threadId` and Telegram UI thread targets use Bot API `message_thread_id`
52
+ - `Thread` is the canonical user/product term for Telegram's tabbed client surface because Telegram clients label the UI as threads. The Bot API names the underlying primitive `Topic`, but product language follows user-perceived client reality, not API naming. Use `topic` only when naming Bot API primitives such as `ForumTopic`, `forum_topic_created`, or topic-management methods; avoid exposing API-centric `topic` terminology in operator UX or product docs.
53
+ - `Telegram instance slot`: A single uppercase `A`-`Z` slot assigned by the extension for instance ordering. New instances advance monotonically through the alphabet and wrap after `Z` only to a free slot; slots are not backfilled and fully occupied live alphabets report exhaustion instead of duplicating a slot. Slots are ordering metadata, not the normal visible title: fresh threads receive a baked 4-6 letter thread name chosen from the slot's curated palette.
54
+ - `Telegram instance thread name`: Human-facing identity metadata for one live/reusable Pi instance. Fresh threads choose one baked 4-6 letter Latin word from a curated five-name palette for the assigned slot letter using provisioning timestamp entropy; the thread title is the thread name itself. Slot letters are fallback labels only for legacy/unnamed threads or exhausted recovery paths. There is no agent-facing thread rename tool and models must not be prompted to self-name topics.
55
+ - `Telegram bus leader`: The live Pi instance that owns `getUpdates` and direct Telegram Bot API transport for one bot token in optional multi-instance mode
56
+ - `Telegram bus follower`: A live Pi instance that registers with the leader, owns its local queue/session state, and routes inbound/outbound Telegram traffic through the leader instead of polling directly
47
57
  - `QueuedAttachment`: Outbound files staged for delivery through `telegram_attach`
48
58
 
49
59
  ## 5. Architectural Decisions
@@ -51,48 +61,73 @@ The core product loop is mobile continuation: start or supervise work in the ter
51
61
  ## 5.1 Flat Domain DAG Shape
52
62
 
53
63
  - The project follows a `Flat Domain DAG`: cohesive bridge domains live as flat `/lib/*.ts` modules, and local imports must form a directed acyclic graph
54
- - `index.ts` stays the single extension entrypoint and composition root for live π/Telegram ports, SDK adapters, and session state
64
+ - `index.ts` stays the single extension entrypoint and composition root for live Pi/Telegram ports, SDK adapters, and session state
65
+ - `index.ts` should compose high-level domain runtimes and ports, not own per-domain policies, loops, capability decisions, lifecycle recovery rules, or constants. When composition needs non-trivial branching over Threaded Mode, bus leader/follower state, sync slices, reconciliation, or session replacement, extract a named runtime/factory into the owning `/lib` domain and leave only port wiring in the entrypoint.
55
66
  - Reusable runtime logic should be split into flat domain files under `/lib`
56
67
  - Opening source-module comments must include `Zones:` tags such as `telegram`, `pi agent`, `tui`, or `shared utils`; these tags replace folder nesting as the quick responsibility map for flat Domain DAG files
57
68
  - Prefer domain-oriented grouping over atomizing every helper into its own file
58
69
  - Use `shared` sparingly and only for types or constants that genuinely span multiple bridge domains
70
+ - Use `.agents/skills/domain-dag/SKILL.md` for non-trivial architecture audits or boundary refactors, and run `SKILL_DIR=.agents/skills/domain-dag bash .agents/skills/domain-dag/scripts/validate-domain-dag.sh --root .` when validating Domain DAG shape directly
59
71
 
60
72
  ## 5.2 Session And Queue Semantics
61
73
 
62
- - The bridge is session-local, paired to one allowed Telegram user, and owns a local queue aligned with π lifecycle hooks
63
- - The Telegram lock owns only external control/polling. Local extension state and queue runtime are per Pi instance: losing `/telegram-connect` ownership stops live Telegram control here, but must not clear, silence, or stop active/queued preview, final delivery, or dispatch in this instance
74
+ - The bridge is session-local, paired to one allowed Telegram user, and owns a local queue aligned with Pi lifecycle hooks
75
+ - The Telegram lock owns only external control/polling. Local extension state and queue runtime are per Pi instance: losing `/telegram-connect` ownership stops live Telegram control here, but must not clear, silence, or stop active/queued preview, final delivery, or dispatch in this instance. Bus leader lock entries carry a stable `leaderEpoch` minted on acquisition and preserved across heartbeat refreshes; leader-owned Thread Reconciler cleanup/provisioning actions stamp that epoch and skip destructive side effects if current leadership differs.
76
+ - The Telegram lock is always the transport ownership lock. In classic/private-chat mode, transport ownership is the whole bridge ownership and `/telegram-connect` from another live Pi should offer the classic takeover confirmation. In Threaded Mode, transport ownership is the bus leader role and `/telegram-connect` from another live Pi should register as follower instead of offering takeover while the leader is live. If follower registration is not applicable because Threaded Mode is disabled or unavailable, fall back to the classic takeover path.
77
+ - BotFather private-chat Threaded Mode is the public switch for the local multi-instance bus. There is no separate public `telegram.json` bus switch: classic/private-chat singleton DM behavior is the base product mode, and when Telegram reports Threaded Mode available the bridge enables leader/follower bus behavior. If Telegram reports Threaded Mode unavailable or thread operations prove it unavailable, the bridge returns to classic singleton behavior as a first-class mode. Do not inject slot/thread rename guidance in classic mode. Blocked instances should not silently register as followers unless the effective runtime mode is Threaded Mode.
78
+ - In Threaded Mode, exactly one live leader owns `getUpdates` and direct Telegram Bot API calls for one bot token. `/telegram-connect` means join the local Telegram organism: if no live leader exists the instance becomes leader; if a live leader exists the terminal-visible instance registers as follower; ordinary connect must not offer manual takeover while the leader heartbeat is live. Leadership is an ephemeral transport role, not permanent ownership by the first terminal-visible Pi instance. Followers must be started by the operator as real Pi processes, register over the local bus, heartbeat, receive only their routed inbound updates, and send outbound replies/previews/files/chat-actions through the leader transport; Telegram must not spawn hidden Pi processes, launch follower subprocesses, or expose a slash command that creates a new local Pi instance. If a leader reloads while followers stay alive, followers must treat rejected/unknown/unauthorized heartbeats as a registration-loss signal and optimistically re-register with the currently active leader before considering promotion; after a short grace window without successful re-registration, a live follower may promote itself and force-acquire transport leadership so the organism degrades forward instead of staying split-brain. Terminal status must not remain `follower` while the leader registry can no longer route that follower's thread. Heartbeat pruning is liveness bookkeeping, not permission to delete, mark offline, or send a Telegram-visible disconnected notice for a follower's thread; preserve active follower tabs/bindings across transient reload/reconnect incidents and reserve destructive teardown/offline transitions for explicit disconnect or confirmed reconciliation cases. If the leader dies, a live follower should eventually be able to take over through stale-leader election/recovery so the local multi-instance organism keeps running. Explicit `/telegram-disconnect` is a manual thread teardown: a leader deletes its own bound thread before releasing transport ownership, while a follower asks the leader to delete its assigned thread through scoped bus API before unregistering. Status is reactive to the effective runtime role and bot capability: classic/non-thread mode shows ordinary `telegram connected`, active bus leadership shows `telegram leader`, and registered followers show `telegram follower`; stable multi-instance truth belongs in the bus role (`leader`/`follower`), while lifecycle is reserved for transient recovery state such as `electing` and must not duplicate stable roles with labels like `leader-active`. When leadership or Threaded Mode availability changes at runtime, the status line and `/telegram-status` must converge promptly instead of preserving a stale role label.
79
+ - Runtime routing binds to a live `instanceId`; current thread identity should use explicit owner records (`leader`, `manual-follower`, `pending-topic`) rather than hiding ownership direction in legacy string keys. Compatibility keys may be derived internally, but stale session records must not be treated as live owners. A live instance owns at most one active bound thread; provisioning a new target for the same `instanceId` must remove older current-state bindings and close duplicate Telegram threads when possible. A reload creates a new runtime `instanceId`; if the previous leader slot is still known/alive, keep it occupied in reservations and assign the new leader the next free slot instead of creating a duplicate same-letter tab or blocking startup on Telegram UI convergence.
80
+ - 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. 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.
81
+ - Instance slots are extension-owned ordering metadata. Allocate new slots monotonically `A` → `B` → ... → `Z` → `A` when a new Telegram thread is truly needed. Reclaim only current pending bindings; do not reclaim inactive stale/offline/failed history because it may point at closed/deleted Telegram tabs. Preserve a slot on current binding/thread reuse. If all current bindings disappear, continue from the compact `bot.lastSlot` cursor in `state.json` instead of retaining old session/thread records. TTL-pruned `pendingProvisions` are scratchpad state: they reserve in-flight provisioning slots and protect cleanup planning, not durable routing history. The alphabet cap is a feature — compact single-letter tabs are scannable.
82
+ - 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 and live diagnostics from the bound thread record; 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.
83
+ - BotFather private-chat Threaded Mode treats `All` as an aggregate/control surface, not a durable `General` thread and not a process launcher. Leader activation creates/reuses a leader thread in the private bot DM (chat = allowedUserId) proactively. If all threads are closed and the owner writes from `All`, a first new unbound thread should be reclaimed by the leader when the leader lacks an active bound thread. Later unknown threads require explicit routing or restore; creating another Pi instance is a manual terminal action followed by `/telegram-connect`.
84
+ - Unbound thread detection: when a message arrives from the owner in an unknown `message_thread_id` while Threaded Mode is active, the default runtime first reclaims it for the leader if the leader has no active bound thread and routes the prompt locally. Later unknown threads are preserved by default and receive an in-thread reroute chooser that can send the captured prompt to a live thread or restore a stale leader/follower binding through explicit restore actions; destructive cleanup happens only after explicit user confirmation and through `thread-reconciler`. If the operator wants another Pi instance, they start Pi in a terminal and run `/telegram-connect`; Telegram-originated hidden auto-spawn and `/thread`-style process launch flows are intentionally absent. If Threaded Mode is unavailable, the message is processed normally through classic routing.
85
+ - Telegram extension work should not hold Pi's core agent lifecycle hostage once Pi has produced the semantic result. For Telegram-originated turns, final Telegram delivery, attachment upload, and transport cleanup are extension-owned side effects; schedule them off the critical `agent_end` path when ordering can still be preserved, record failures as runtime events, and keep dispatch of the next Telegram turn behind the delivery task when needed to avoid interleaving previews/finals.
86
+ - Telegram 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. 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.
87
+ - 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.
88
+ - The multi-instance bus uses private bot DMs with Topics enabled (BotFather Threaded Mode). 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 BotFather private-chat Threaded Mode.
89
+ - 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 private BotFather 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.
90
+ - Local bus IPC is a local-only trust boundary: private socket directories/files are necessary but not the whole authorization model; never let arbitrary local processes inject prompts, callbacks, API sends, or artifact routes.
64
91
  - Queue admission is explicit and validated: immediate commands, control lane, priority lane, and default lane must preserve allowed kind/lane pairings
65
- - Dispatch is gated by active turns, pending dispatch, unsettled control work, compaction, `ctx.isIdle()`, and π pending messages; dispatched prompts remain queued until `agent_start` consumes them
66
- - Telegram `/compact` owns a native `typing` keepalive for the compaction window so phone clients show activity between the started/completed notices; stop it on both completion and failure
92
+ - Dispatch is gated by active turns, pending dispatch, unsettled control work, compaction, `ctx.isIdle()`, and Pi pending messages; dispatched prompts remain queued until `agent_start` consumes them
93
+ - Telegram `/compact` owns a native `typing` keepalive for the compaction window so phone clients show activity between the started/completed notices; stop it on both completion and failure. Automatic/session compaction may start native typing only when an active Telegram turn exists, and must reuse that turn target; startup, reload, connect, restore, and idle/background compaction must not create visible typing by themselves.
67
94
  - `/stop`, `/abort`, `/next`, and `/continue` have distinct contracts: reset queue and abort; abort while preserving queue; force next queued turn; enqueue a control-lane `continue` resume prompt without folding queued prompts into history
68
95
  - Abort-history mode is scoped to Telegram-owned active turns. Internally this is `foldQueuedPromptsIntoHistory`: local/non-Telegram agent starts after abort clear it so the next Telegram prompt appends to the local queue instead of absorbing older queued turns as history
69
96
  - `/start`, `/help`, and `/status` open the unified command-help/status-row/control menu; `/model`, `/thinking`, and `/queue` jump to sections directly; visible bot commands are `/start`, `/compact`, `/next`, `/continue`, `/abort`, `/stop`
70
97
  - Command/menu emoji are fixed UI adornments owned by the `commands` map; do not add a persisted emoji toggle or Settings menu until there is a real setting to own
98
+ - UI emoji semantics live in `docs/ui-style.md` as the project-level emoji registry. Before adding a new UI emoji, reuse an existing registered meaning or update the registry in the same change. Use `🧵` consistently as the canonical Telegram/Pi thread marker in thread chooser headings, thread lifecycle copy, and target-routing status text; do not decorate every concrete thread-target button with it when the button label is already the thread name or slot. Operator-facing Threaded Mode routing copy should use the `Pi` glyph for Pi instance/thread/session concepts; reserve `Pi` spelling for product prose, package/API names, and surrounding docs where the wordmark is clearer.
71
99
  - Telegram `reply_to_message` context is prompt-only and must not affect slash-command parsing; when Telegram includes `rich_message` blocks on a quoted rich reply, extract plain text from those blocks before falling back to raw `text`/`caption` so prompt context does not leak raw Rich Markdown source
72
- - Long-lived timers, pollers, watchers, and deferred queue dispatch must be session-bound and avoid stale live π contexts after session replacement
100
+ - Long-lived timers, pollers, watchers, and deferred queue dispatch must be session-bound and avoid stale live Pi contexts after session replacement. Pi session replacement such as terminal `new` changes agent context, not Telegram bus membership: a registered follower should preserve its bus registration and refresh only its live session context instead of disconnecting from Telegram.
73
101
  - Do not add Telegram commands that imitate Pi interactive session replacement, navigation, or TUI rendering through private internals, ANSI terminal clearing, raw TTY injection, or a shadow `pi` subprocess. Features such as a real Telegram `/new` require a public Pi API that runs the same session-replacement path as the terminal command.
74
102
  - In-flight `/model` switching is limited to Telegram-owned active turns; if a tool call is active, abort is delayed until the tool finishes
75
103
 
76
104
  ## 5.3 Telegram Delivery Semantics
77
105
 
78
106
  - Assistant and guest replies use Telegram-native Rich Markdown via Rich Message APIs, not Markdown→HTML conversion. Bridge-owned UI surfaces such as commands, menus, status, queue controls, and sections should keep explicit Telegram HTML/plain rendering by default because readability and maintainability are higher there. Companion sections may explicitly choose Markdown, HTML, or plain text per view. Keep native Rich Markdown source close to model-authored Markdown, but normalize Bot-API-fragile equivalents when evidence shows a Telegram parser/client edge, such as space-after-marker blockquotes (`> quote` -> `>quote`) and dollar-prefixed ticker atoms (`$BLDR` -> `\$BLDR`) outside code fences/spans
79
- - Use `docs/telegram-bot-api-rich-messages.md` as the local Bot API/Rich Messages reference for native Rich Markdown work
107
+ - Use `.agents/skills/telegram-bot/SKILL.md` and its `api.md` reference for native Rich Markdown, Bot API topic transport, and transport capability checks
80
108
  - Formula guidance belongs in the Telegram-turn prompt contract: use `$...$` for inline math and `$$...$$` for block math; backticks intentionally render formulas as literal code
81
109
  - Real code blocks must stay literal and escaped
82
- - `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 for explicit local/TUI delivery requests only when this π instance owns `/telegram-connect`. `telegram_message` is the first-class direct Telegram Markdown text tool for local/TUI prompts and is also gated by `/telegram-connect`; 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
110
+ - `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
83
111
  - Telegram delivery strips top-level HTML comments from preview/final text; column-zero top-level `<!-- telegram_voice ... -->` and `<!-- telegram_button ... -->` blocks are special outbound comments handled after `agent_end` without requiring agent-side transport tool calls, while comments inside code, quotes, lists, or indented examples stay literal
84
- - Telegram prompt guidance is layered: unconfigured sessions receive no bridge suffix, local/TUI prompts receive only explicit direct-delivery guidance, and Telegram-originated turns receive the full inbound/phone-width/output-action contract
85
- - `telegram_voice` and `telegram_button` are not π 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
112
+ - 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.
113
+ - `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
86
114
  - Voice reply policy and prompt context are owned by pi-telegram's `telegram.json` `voice.replyMode`: missing/invalid config behaves as `manual` but does not add a `[voice]` prompt-context block; only an explicit valid `voice.replyMode` renders context. Render a single voice field as `[voice] reply mode: manual|mirror|always`, and render multiple fields as a `[voice]` list; place voice context after `[outputs]` when handler output exists, otherwise after `[attachments]`; provider prompt contributions are optional provider-specific additions, not the default policy channel
87
115
  - 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.
116
+ - Agent activity status is native-only for the release path: Telegram uses `sendChatAction(typing)` / product `...active` status as the automatic work signal. It belongs to real agent work only: active Telegram turns, tool/reasoning/message activity during those turns, confirmed manual `/compact`, and automatic compaction tied to an active Telegram turn. Do not revive startup/connect/reload typing or add extra in-chat work surfaces without a new explicit design decision.
88
117
  - Voice reply mode Settings UI standard: the top-level Settings row is `👄 Voice reply: hidden|manual|mirror|always`; `hidden` is the true default and means no valid `voice.replyMode` is persisted, behavior is manual, and no voice policy is added to prompt context; explicit `manual` behaves the same operationally but renders reply-mode context. The submenu title is `👄 Voice reply mode:`; choice buttons use lowercase labels with a model-style active dot (`🟢 hidden`, `🟢 mirror`) rather than per-mode emoji; the explanatory submenu body uses compact HTML-code bullets such as `<code>-</code> <code>hidden</code> (default): ...`. Preserve this wording/icons unless the operator explicitly asks to redesign it
89
118
  - 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
90
- - `telegram_voice` text is arbitrary TTS-target text: use body form for multiline text, `<!-- telegram_voice text="Short summary" -->` for explicit one-line text, or `<!-- telegram_voice: Short summary -->` for one-line text with no attributes
119
+ - `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.
91
120
  - `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
92
121
 
93
122
  ## 6. Engineering Conventions
94
123
 
95
- ## 6.1 Validation Hotspots
124
+ ## 6.1 Rule Placement And Cross-Cutting Standards
125
+
126
+ - Keep globally reusable rules as independent bullets in the most general applicable section of `AGENTS.md`; do not hide a cross-cutting rule inside one local feature paragraph just because that feature revealed it.
127
+ - Local/domain bullets should state how a general rule applies there, or record domain-specific exceptions; they should not be the only source for a rule that applies to multiple tools, transports, domains, or docs surfaces.
128
+ - Pi-facing tool results shown in compact tool rows should start with exactly one leading newline (`\nResult`) so the result is visually separated from the tool-call header by one blank line without excessive vertical spacing.
129
+
130
+ ## 6.2 Validation Hotspots
96
131
 
97
132
  - Treat queue handling, compaction interaction, and lifecycle-hook state transitions as regression-prone areas; validate them after changing dispatch logic
98
133
  - Route important runtime failures through the recent runtime event recorder so `/telegram-status` remains useful for post-mortem debugging, not just transient status-bar errors
@@ -102,33 +137,32 @@ The core product loop is mobile continuation: start or supervise work in the ter
102
137
  - 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
103
138
  - Flatten nested Markdown quotes into indented single-blockquote output because Telegram does not render nested blockquotes reliably
104
139
 
105
- ## 6.2 File And Naming Style
140
+ ## 6.3 File And Naming Style
106
141
 
107
142
  - Keep comments and user-facing docs in English unless the surrounding file already follows another convention
108
143
  - 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
109
144
  - Name extracted `/lib` modules and mirrored `/tests` suites by bare domain when the repository already supplies the Telegram scope; prefer `queue.ts`, `updates.ts`, and `queue.test.ts` over redundant `telegram-*` filename prefixes. Exception: the concrete Bot API transport domain is named `telegram-api.ts` / `telegram-api.test.ts` to avoid ambiguity with the public `/api/*.ts` package membranes
110
- - Keep test helpers with the mirrored domain suite by default because test files mirror module-domain boundaries; introduce shared `tests/fixtures` only when multiple domain suites truly reuse the same setup
145
+ - Keep test helpers with the mirrored domain suite by default because test files mirror module-domain boundaries; introduce shared `tests/fixtures` only when multiple domain suites truly reuse the same setup. A `tests/<domain>.test.ts` file should primarily protect `lib/<domain>.ts`; this includes `tests/runtime.test.ts`, which should protect `lib/runtime.ts` rather than act as a general integration bucket. Cross-domain runtime flows belong in the single shared `tests/integration.test.ts` suite. Do not create narrow `*.integration.test.ts` files unless a future repeated setup boundary becomes large enough to justify splitting the one shared integration suite
111
146
  - Prefer targeted edits, keeping `index.ts` as the orchestration layer and moving reusable logic into flat `/lib` domain modules when a subsystem becomes large enough to earn extraction
112
147
  - 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
113
148
  - 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
114
149
 
115
- ## 6.3 Current Domain Ownership Snapshot
150
+ ## 6.4 Current Domain Ownership Snapshot
116
151
 
117
152
  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.
118
153
 
119
154
  - Scheduling and lifecycle: `queue`, `runtime`, `lifecycle`, `locks`
120
- - Telegram transport and inbound flow: `api`, `polling`, `updates`, `routing`, `media`, `turns`, `inbound`, `config`, `setup`
155
+ - Telegram transport, polling, bus, sync, and inbound flow: `api`, `polling`, `bus`, `bus-api`, `bus-leader`, `bus-follower`, `ownership`, `target`, `sync`, `thread-reconciler`, `threads`, `updates`, `routing`, `media`, `turns`, `inbound`, `config`, `setup`
121
156
  - Response surfaces: `preview`, `replies`, `rendering`, `keyboard`, `outbound-markup`, `outbound-attachments`, `outbound`, `voice`, `status`
122
157
  - Controls and application menu UI: `commands`, `menu`, `menu-model`, `menu-thinking`, `menu-status`, `menu-queue`, `model`, `prompts`
123
158
  - Extension platform: `sections` owns section registry, token mapping, callback dispatch, context building, and its globalThis bridge; `voice` owns the voice-provider registry and its globalThis bridge
124
159
  - 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
125
160
 
126
- ## 6.4 Entrypoint And Import Boundaries
161
+ ## 6.5 Entrypoint And Import Boundaries
127
162
 
128
163
  - 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
129
164
  - Preview/final delivery ordering is release-critical: finalization must wait 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
130
165
  - Live Rich Draft observation: `sendMessageDraft(..., undefined)` after a persisted final Rich Message can appear in Telegram clients as a separate animated three-dot draft block before dissolving. Do not use post-final draft-clear for assistant finalization; let the persisted final `sendRichMessage` replace/complete the user-visible lifecycle and reset local preview state only.
131
- - `RichBlockThinking` / `<tg-thinking>` is draft-only (`sendRichMessageDraft`) and may be useful for a future explicit pre-token preloader, but it is not a persisted final-message primitive and should not be mixed into release-critical finalization behavior without separate UX tests.
132
166
  - Keep Telegram prompt guidance compact and operational. Do not add format-specific steering for native Rich Markdown features unless the model needs a real bridge-specific rule such as formula delimiters or hidden outbound action syntax.
133
167
  - Keep direct `node:*` file-operation dependencies out of `index.ts` when an owning domain exists; the entrypoint should compose ports while domains own local filesystem details such as temp-dir preparation, attachment stats, and turn image reads
134
168
  - In `index.ts`, prefer namespace imports for local bridge domains so orchestration reads as domain-scoped calls such as `Queue.*`, `Turns.*`, and `Rendering.*` instead of long flat import lists
@@ -141,17 +175,18 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
141
175
  ## 7. Operational Conventions
142
176
 
143
177
  - When Telegram-visible behavior changes, sync `README.md` and the relevant `/docs` entry in the same pass
178
+ - Dead-code source of truth is real reachability from composition roots, package exports, tests, registered commands/tools/hooks, and documented public APIs — not whether code is implemented or recently useful. Proactively remove unused mechanisms when their only evidence is historical intent. Treat `tsc --noUnusedLocals --noUnusedParameters` as the diagnostic lead source, then whitelist only deliberate exported public contracts or test fixtures with documented reachability.
144
179
  - When durable runtime constraints or repeat bug patterns emerge, record them here instead of burying them in changelog prose
145
180
  - When fork identity changes, keep `README.md`, package metadata, and docs aligned so the published package does not point back at stale upstream coordinates
146
181
  - README positioning should emphasize `/start` as the primary Telegram operator menu and keep reaction shortcuts secondary. Reactions are useful queue affordances, but menu controls are the core CLI-to-Telegram bridge.
147
- - Document configuration knobs without UI in the root README when they affect bootstrap, networking, transport limits, or prompt context; currently this includes token env bootstrap, Node env proxy mode, inbound/outbound size limits, and `time`.
182
+ - Document configuration knobs without UI in the root README when they affect bootstrap, networking, transport limits, or prompt context; currently this includes token env bootstrap, Node env proxy mode, Telegram network-family policy, inbound/outbound size limits, and `time`.
148
183
  - Keep extension-local standards self-contained: shared patterns such as command templates may evolve independently in multiple extensions, but pi-telegram docs/changelog should describe the standard without naming sibling extension implementations as dependencies or authorities.
149
184
  - Work only inside this repository during development tasks; updating the installed Pi extension checkout is a separate manual operator step, not part of normal in-repo implementation work
150
185
 
151
186
  ## 8. Integration Protocols
152
187
 
153
188
  - Telegram API methods currently used include polling, message editing, draft streaming, callback queries, reactions, file download, and media upload endpoints
154
- - π integration depends on lifecycle hooks such as `before_agent_start`, `agent_start`, `message_start`, `message_update`, and `agent_end`
189
+ - Pi integration depends on lifecycle hooks such as `before_agent_start`, `agent_start`, `message_start`, `message_update`, and `agent_end`
155
190
  - `ctx.ui.input()` provides placeholder text rather than an editable prefilled value; when a real default must appear already filled in, prefer `ctx.ui.editor()`
156
191
  - For `/telegram-setup`, prefer the locally saved bot token over environment variables on repeat setup runs; env vars are the bootstrap path when no local token exists, and persisted `telegram.json` writes must remain atomic plus private because status/setup/polling paths may read it concurrently
157
192
  - Command help plus prompt-template commands and status/model/thinking/queue controls are driven through `/start`'s Telegram inline application menu and callback queries; the Queue button shows the queued-item count, model-menu scope/pagination controls stay at the top under Main menu, the model pagination indicator opens a compact page picker, and thinking-menu text stays a compact heading because the current level is marked by button state; `/status`, `/model`, `/thinking`, and `/queue` are hidden compatibility shortcuts
@@ -162,7 +197,7 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
162
197
  - Long Telegram text split recovery belongs to `text-groups`: keep it conservative, short-debounced, same chat/user/message-id contiguous, and gated by near-limit human text so normal rapid follow-ups and slash commands stay separate
163
198
  - Public API boundaries live in [`docs/public-api.md`](./docs/public-api.md): companion extensions must use public API domain subpaths such as `@llblab/pi-telegram/sections`, `/voice`, `/inbound`, `/outbound`, and `/updates`; `0.12.0` intentionally removes the published `@llblab/pi-telegram/lib/*.ts` compatibility wildcard
164
199
  - Public handler API matrix: low-level buses use `registerTelegramUpdateHandler(handler)`, `registerTelegramInboundHandler(kind, handler)`, and `registerTelegramOutboundHandler(kind, handler)` without ids; high-level surfaces use stable identity (`registerTelegramSection({ id, ... })`, `registerTelegramVoiceTranscriptionProvider(provider, { id })`, and `registerTelegramVoiceSynthesisProvider(provider, { id })`). Inbound handlers and command-backed outbound handlers use command templates as the standard config contract; built-in outbound buttons use inline keyboards plus callback routing because no polling command execution is needed
165
- - Telegram prompt-template commands are discovered from π slash commands with `source: "prompt"`; π template names are mapped to Bot API-compatible aliases (`fix-tests` → `/fix_tests`), aliases that conflict with built-in bridge commands or hidden shortcuts are not displayed, prompt-template aliases stay out of the Telegram bot command menu, and the bridge expands template files before queueing because extension-originated `sendUserMessage()` bypasses π's interactive template expansion
200
+ - Telegram prompt-template commands are discovered from Pi slash commands with `source: "prompt"`; Pi template names are mapped to Bot API-compatible aliases (`fix-tests` → `/fix_tests`), aliases that conflict with built-in bridge commands or hidden shortcuts are not displayed, prompt-template aliases stay out of the Telegram bot command menu, and the bridge expands template files before queueing because extension-originated `sendUserMessage()` bypasses Pi's interactive template expansion
166
201
  - Unknown callback data not owned by pi-telegram prefixes (`compact:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `section:`, `settings:`) may be forwarded as `[callback] <data>` after built-in handlers decline it; update-handler extensions should follow `docs/callback-namespaces.md` and must not poll the same bot independently
167
202
  - Command templates stay compact and shell-free: no `command` field, no shell execution, inline defaults are allowed as `{name=default}`, `template` may be a string or an ordered composition array, only `args`/`defaults` inherit into leaves, top-level `timeout` wraps composed sequences, stdout pipes to the next step's stdin by default, and multi-step work should use `template: [...]` rather than provider-specific fields; `pipe` is only a legacy local alias
168
203
  - Command-template documentation examples should use portable executable placeholders such as `/path/to/stt` and `/path/to/tts`, not host-local skill paths or machine-specific install locations
@@ -195,5 +230,5 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
195
230
 
196
231
  - Run the smallest meaningful validation for the touched area; `npm test` is the default regression suite once rendering or queue logic changes
197
232
  - For rendering changes, ensure regressions still cover nested lists, code blocks, underscore-heavy text, and long-message chunking
198
- - For queue/dispatch changes, validate abort, compaction, pending-dispatch, and π pending-message guard behavior
233
+ - For queue/dispatch changes, validate abort, compaction, pending-dispatch, and Pi pending-message guard behavior
199
234
  - Sync `README.md`, `CHANGELOG.md`, `BACKLOG.md`, and `/docs` whenever user-visible behavior or real open-work state changes
package/BACKLOG.md CHANGED
@@ -1,21 +1,61 @@
1
1
  # Project Backlog
2
2
 
3
- - [ ] Track evidence-backed Telegram Rich Markdown normalization follow-ups.
4
- - Done when: newly observed Telegram parser-breaking sequences are captured from live/client evidence or minimized fixtures.
5
- - Done when: each confirmed sequence has a conservative normalization or safe-degradation rule covered by tests.
6
- - Done when: unconfirmed speculative rewrites remain out of the delivery path.
7
-
8
- - [ ] Evaluate Rich Draft placeholder heartbeat for long unsafe preview gaps.
9
- - Done when: live/client evidence confirms whether `RichBlockThinking` / `<tg-thinking>` prevents Telegram draft expiry while assistant output is still streaming but no safe Markdown prefix is available.
10
- - Done when: the behavior is compared against the existing typing keepalive lifecycle so Telegram chat action and Rich Draft placeholder states do not fight each other.
11
- - Done when: abort/clear behavior is tested so placeholder drafts do not create the transient confusing block observed after abort.
12
-
13
- - [ ] Track a public Pi session-replacement hook before adding Telegram `/new`.
14
- - [ ] Wait for a safe public Pi API.
15
- - Done when: Pi exposes an API that invokes the same session-replacement path as terminal `/new`.
16
- - Done when: the API covers lifecycle, active-run handling, and TUI rerender semantics.
17
- - [x] Keep unsafe implementation routes rejected.
18
- - Done when: raw TTY injection remains rejected.
19
- - Done when: ANSI terminal clearing remains rejected.
20
- - Done when: private TUI container mutation remains rejected.
21
- - Done when: shadow `pi` subprocess control remains rejected.
3
+ _Current deterministic status: Threaded Mode implementation, native typing/activity status, regression coverage, docs/context reconciliation, typecheck, full tests, pack check, audit, Domain DAG validation, context validation, and core live Threaded Mode smoke are green. This backlog intentionally tracks only release-relevant remaining work: native Windows smoke, evidence-gated Telegram client follow-ups, and upstream Pi API blockers._
4
+
5
+ ## P0 Native Windows Threaded Mode Support
6
+
7
+ Context: Threaded Mode uses a local leader/follower IPC bus. Unix-like platforms use Node `net` over Unix sockets; native Windows uses Node `net` named-pipe paths. The product expectation is identical behavior across both transports: leader/follower registration, heartbeats, forwarded Telegram API calls, thread target preservation, lifecycle cleanup, and shutdown semantics should not depend on socket-vs-pipe transport.
8
+
9
+ Open work:
10
+
11
+ - [ ] Live smoke Threaded Mode on native Windows without WSL.
12
+ - Scope: leader/follower `/telegram-connect`, follower heartbeat, forwarded Bot API calls, restore flows, lifecycle announcements, shutdown cleanup, and reconnect/reload behavior.
13
+ - Baseline: deterministic path tests run everywhere, and a Windows-only named-pipe roundtrip regression runs when the suite executes on `win32`. Live Windows smoke remains unavailable in this environment.
14
+ - [ ] If Windows live smoke exposes pipe-specific behavior, add a minimized regression at the bus transport boundary before changing higher-level Threaded Mode logic.
15
+
16
+ Done when: Threaded Mode leader/follower operation works on native Windows with the same safety guarantees as Unix-like systems, and unsupported transport assumptions are covered by tests/docs.
17
+
18
+ ## P1 Evidence-Backed Telegram Client Follow-Ups
19
+
20
+ Context: The release should avoid speculative live-test matrices. Deterministic coverage already protects target propagation, native typing/activity scoping, hot Threaded Mode upgrade/downgrade, delivery routing, and core leader/follower behavior. Future Telegram-client quirks should be handled only when there is concrete evidence or a minimized fixture.
21
+
22
+ Open work:
23
+
24
+ - [ ] Capture any new Telegram client or Bot API behavior that contradicts the documented Threaded Mode contract.
25
+ - [ ] Add a focused regression or documented client caveat only for confirmed behavior.
26
+ - [ ] Keep one-off live environment names, thread names, and operator-specific observations out of repository context unless they demonstrate a general product issue.
27
+
28
+ Done when: new client quirks are either fixed with targeted coverage or documented as evidence-backed exceptions, without keeping broad manual smoke matrices in the backlog.
29
+
30
+ ## P1 — Evidence-Backed Rich Markdown Normalization
31
+
32
+ Context: Native Rich Markdown is the default assistant delivery path. Existing regressions cover known parser/client edges such as space-after-marker blockquotes, dollar-prefixed ticker atoms, list indentation, code fences, links, display math normalization, and long-message splitting. Further rewrites should be evidence-driven, not speculative.
33
+
34
+ Open work:
35
+
36
+ - [ ] Capture any new Telegram parser-breaking sequence from live/client evidence or a minimized fixture.
37
+ - [ ] Add a conservative normalization or safe-degradation rule only for confirmed sequences.
38
+ - [ ] Keep unconfirmed speculative rewrites out of the delivery path.
39
+
40
+ Done when: newly observed Rich Markdown failures have minimized fixtures and targeted regressions, while stable rendering behavior remains unchanged for unsupported guesses.
41
+
42
+ ## Blocked — Same-Thread Telegram `/new`
43
+
44
+ Blocked: upstream Pi core API. Issue: https://github.com/earendil-works/pi/issues/5952
45
+
46
+ Context: Threaded Mode manual followers are separate visible Pi processes. Same-thread `/new` is a different feature: replacing the current Pi session inside the same Telegram thread. Extension-only hacks are rejected because they would desynchronize Pi lifecycle/TUI semantics.
47
+
48
+ Required upstream shape:
49
+
50
+ - `pi.newSession(...)` or `pi.requestSessionReplacement(...)` callable from trusted extension runtime code.
51
+ - Must use the same session-replacement path as the terminal command, including normal `session_shutdown` / `session_start` lifecycle.
52
+
53
+ Constraints:
54
+
55
+ - Do not store stale `ExtensionCommandContext`.
56
+ - Do not inject TUI input.
57
+ - Do not spawn a shadow `pi` subprocess.
58
+ - Do not mutate session files directly.
59
+ - Do not route through `pi.exec`; it is shell execution, not a Pi slash-command dispatcher.
60
+
61
+ Done when: `/new` in the current Telegram thread performs an official same-instance session replacement, preserves the thread binding, rebinds after lifecycle restart, reports success/cancellation in the same thread, and has regressions for active turns, pending Pi messages, queue state, preview cleanup, cancellation, failure, and success.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.0: Thread Mode
4
+
5
+ - `[Thread Mode]` Added BotFather private-chat Thread Mode while keeping classic private DM as the base mode. BotFather Thread Mode is now the bus switch: in ordinary private-DM mode, one Pi session owns the bot; when Telegram exposes Threads, one live leader owns polling/Bot API transport and visible follower Pi processes join explicitly with `/telegram-connect`. Impact: operators can choose either a simple single-instance bot or a multi-thread development workspace without competing pollers or hidden process spawning.
6
+ - `[Capability Switching]` Reworked Thread Mode availability around private-bot evidence: `getMe.has_topics_enabled`, incoming `message_thread_id`, and private-thread operation success/failure. A 5-second monitor can hot-upgrade classic mode to Thread Mode and hot-downgrade back to classic without `/reload`. Impact: BotFather Thread Mode behaves as a progressive enhancement instead of a startup-only assumption.
7
+ - `[Thread Routing]` Preserved per-instance ownership for queues, active turns, model/tool state, previews, menus, lifecycle hooks, replies, files, voice, buttons, reactions, and command/control replies. Impact: leader and follower threads behave like scoped Telegram workspaces rather than views over one shared queue.
8
+ - `[Thread Lifecycle]` Added durable current-state thread bindings with explicit owners, stable slots, compact baked thread names, reservations, and proof-before-delete cleanup through `thread-reconciler`. Impact: reloads, reconnects, stale tabs, restores, and cleanup avoid duplicate live threads and avoid deleting uncertain targets.
9
+ - `[Leader/Follower Recovery]` Added recovery for reloads, reconnects, follower heartbeat loss, promotion, explicit disconnect, stale thread cleanup, and same-profile follower resume. Impact: Thread Mode degrades forward across local process churn while preserving the intended thread binding where possible.
10
+ - `[Native Activity Status]` Kept Telegram work indication native-only through Bot API `sendChatAction(typing)` / Telegram client `...active` status. Custom progress/activity/thinking message surfaces are not part of the release path. Impact: long-running work uses the Telegram-native affordance instead of adding extra chat noise.
11
+ - `[Compaction Activity]` Scoped typing to real work: active Telegram turns, tool/reasoning/message activity during those turns, confirmed manual `/compact`, and automatic compaction only when it belongs to an active Telegram turn. Thread-scoped typing preserves the concrete thread target and mirrors to `All` as aggregate activity. Impact: compaction shows native activity when it should, without reviving startup/connect/reload typing.
12
+ - `[Thread-Scoped Delivery]` Propagated `{ chatId, threadId? }` across inbound routing, replies, previews/drafts, typing, voice, attachments, menus, sections, callbacks, reactions, media groups, slash commands, and follower-routed Bot API calls. Impact: classic DM delivery and thread delivery share one target-scoped transport contract.
13
+ - `[Replies And Drafts]` Anchored the first assistant reply block to the prompting message in Thread Mode and kept later chunks sequential; preview rollover preserves the active turn's source reply metadata. Telegram Desktop may still omit the visual reply header despite the same correct payload that mobile renders. Impact: thread conversations keep local reply context without stacking headers on every chunk.
14
+ - `[Thread UX]` Improved unbound-thread reroute/restore flows, cleanup of temporary chooser surfaces, connected/offline notices, status/menu role indicators, and routing controls. Impact: operator-facing Thread Mode surfaces are more recognizable and less likely to leave stale controls behind.
15
+ - `[Proactive Push]` Allowed Thread Mode followers to send successful local non-Telegram final replies through the leader transport when `proactivePush` is enabled. Impact: follower-owned local results can reach the assigned thread without granting unrelated non-owner processes Telegram delivery.
16
+ - `[Agent Responsiveness]` Moved successful Telegram turn follow-up delivery work into extension-owned background tasks. Impact: Pi can leave the working state sooner after producing the final answer while preserving Telegram delivery ordering.
17
+ - `[Telegram API Transport]` Added `PI_TELEGRAM_NETWORK_FAMILY=auto|ipv4|ipv6|ipv4-fallback`; the default `ipv4-fallback` retries transport-level dual-stack failures through Node's IPv4-only HTTPS path after native `fetch`. JSON calls, multipart uploads, and file downloads now share one transport helper with richer redacted diagnostics. Impact: hosts with broken IPv6 can keep polling, typing, drafts, downloads, uploads, and direct tools working without global DNS changes.
18
+ - `[Security]` Hardened local bus authorization with leader-minted secrets, derived private endpoints, allowed-user checks, target-scoped follower Bot API allowlists, and liveness refreshes around forwarded updates/API calls. Impact: followers can use their assigned thread without gaining arbitrary bot control.
19
+ - `[Windows IPC]` Added native Windows named-pipe path selection for the local leader/follower bus alongside Unix socket support. Impact: Thread Mode has an intended native Windows transport path, with live Windows smoke still tracked separately in the backlog.
20
+ - `[Diagnostics]` Expanded `/telegram-status`, `tmp/telegram/state.json`, and redacted `tmp/telegram/logs.jsonl` around runtime role, roster, capability state, reservations, thread reconciliation, and transport health, without making diagnostics files routing authority. Impact: operators and agents can inspect Thread Mode health without depending on stale history.
21
+ - `[Validation]` Split Thread Mode coverage across focused bus, leader, follower, runtime, integration, routing, lifecycle, and delivery tests. Impact: the release behavior is pinned by domain-owned regressions rather than one oversized integration bucket.
22
+ - `[Architecture]` Moved Thread Mode logic out of `index.ts` into focused domains for bus protocol, leader runtime, follower runtime, thread bindings, synchronization, polling/capability switching, and reconciliation. The separate public `telegram.json` bus switch was removed from the product model and implementation. Impact: the public surface stays small, classic mode remains the base mode, BotFather Thread Mode is the multi-instance switch, and product language consistently says Thread while Bot API topics remain implementation details.
23
+
3
24
  ## 0.17.5: Screenshot Refresh
4
25
 
5
26
  - `[Docs]` Refreshed the package screenshot.
@@ -31,7 +52,7 @@
31
52
  - `[Preview UX]` Streaming preview is now a thin Rich Draft lifecycle controller with serialized flushes, no default debounce, no assistant rendering dependency, and no post-final draft-clear call. Impact: live Telegram clients get smoother draft updates and avoid duplicate final messages, blank finalization gaps, or the transient animated three-dot block observed during draft clearing.
32
53
  - `[UI Boundary]` Bridge-owned commands, menus, status messages, queue controls, buttons, and sections remain explicit Telegram HTML/plain UI by default, while companion sections can opt into Markdown/HTML/plain per view. Impact: native Rich Markdown improves model-authored replies without making hand-authored bot UI harder to maintain.
33
54
  - `[API And Limits]` Added typed Rich Message send/draft helpers, disabled automatic entity detection for assistant/guest Rich Markdown, and split native Markdown at Telegram Rich Message character/block limits while keeping reply metadata on the first chunk and reply markup on the final chunk. Impact: technical output avoids accidental entities and long replies stay within Bot API limits.
34
- - `[Docs And Tests]` Added `docs/telegram-bot-api-rich-messages.md` as the durable local Bot API reference, updated README/outbound/public API/sections/architecture/prompt/AGENTS guidance, kept formula prompting compact around `$...$` / `$$...$$`, and added regressions for native delivery, guest replies, preview lifecycle, split replies, and the UI/compat rendering boundary. Impact: the release behavior is documented, covered, and easier to preserve.
55
+ - `[Docs And Tests]` Added a local Bot API Rich Messages reference, updated README/outbound/public API/sections/architecture/prompt/AGENTS guidance, kept formula prompting compact around `$...$` / `$$...$$`, and added regressions for native delivery, guest replies, preview lifecycle, split replies, and the UI/compat rendering boundary. Impact: the release behavior is documented, covered, and easier to preserve.
35
56
 
36
57
  ## 0.16.6: Telegram Review Hardening Hotfix
37
58
 
@@ -206,7 +227,7 @@
206
227
 
207
228
  ## 0.10.5: Queue Continuity And Input Resilience Hotfix
208
229
 
209
- - `[Compaction]` `/compact` completion and failure callbacks now request deferred queue dispatch instead of dispatching immediately. Impact: queued Telegram turns resume after compaction state and π idle/pending-message state have a chance to settle.
230
+ - `[Compaction]` `/compact` completion and failure callbacks now request deferred queue dispatch instead of dispatching immediately. Impact: queued Telegram turns resume after compaction state and Pi idle/pending-message state have a chance to settle.
210
231
  - `[Text Groups]` Long-text split recovery is more aggressive where Telegram chunking actually drifts: the debounce is rounded to 1s, the conservative 3600-character start threshold is preserved, and continuation messages can span a much wider message-id gap while staying scoped to the same chat/user and non-command text. Impact: very large pasted prompts are more likely to arrive as one agent turn instead of several fragmented turns.
211
232
  - `[Runtime Status]` Typing-loop and prompt-dispatch status updates are now best-effort and record stale-context failures as structured runtime events. Impact: status/Running indicators remain resilient after error paths without hiding diagnostics.
212
233
  - `[Tests]` Added regressions for deferred compact dispatch, stale status failures in typing/dispatch paths, and many-part split-text grouping.
@@ -278,7 +299,7 @@
278
299
 
279
300
  ## 0.9.6: Runtime Adapter Positioning
280
301
 
281
- - `[Package]` Repositioned the package description from "Better Telegram DM bridge extension for π" to "Telegram runtime adapter for π". Impact: package metadata now reflects the runtime adapter/operator-console role rather than a narrow pipe metaphor.
302
+ - `[Package]` Repositioned the package description from "Better Telegram DM bridge extension for Pi" to "Telegram runtime adapter for Pi". Impact: package metadata now reflects the runtime adapter/operator-console role rather than a narrow pipe metaphor.
282
303
  - `[Telegram API]` Introduced `TELEGRAM_API_BASE` for the Bot API endpoint and documented native HTTP/HTTPS proxy operation through `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, and explicit `NODE_USE_ENV_PROXY=1` / `--use-env-proxy` enablement. Impact: users behind corporate proxies, local HTTP tunnels, or restricted networks get a zero-runtime-dependency proxy path without replacing native `fetch`; SOCKS5 remains outside the zero-dependency core.
283
304
  - `[Dependencies]` Refreshed the lockfile transitive dependency set so `npm audit` clears current `fast-uri` and `fast-xml-builder` advisories inherited through development peer installs. Impact: the full `npm run validate` pipeline passes without changing runtime dependencies.
284
305
  - `[README]` Restructured the user entrypoint around install → connect → use → core features → docs, then consolidated examples, terminology, proxy setup, `PI_CODING_AGENT_DIR`, and other environment-only configuration around the runtime-adapter/operator-console model. Impact: first-time users get a clearer path from installation to operation, while vivid examples and non-UI runtime knobs stay discoverable.
@@ -290,7 +311,7 @@
290
311
  - `[Final Delivery]` Final Markdown preview replacement now catches Telegram transport failures and returns a normal fallback signal; the agent-end delivery path records final-text delivery failures and continues cleanup, attachment handling, and queue dispatch. Impact: a failed `editMessageText` at `agent_end` no longer breaks the bridge lifecycle or blocks the next queued Telegram turn.
291
312
  - `[Diagnostics]` Preview and final delivery failures now flow through the runtime event recorder with compact phase metadata. Impact: `/telegram-status` can show recent transport failures without dumping noisy stack traces into the extension runner.
292
313
  - `[Tests]` Added preview and queue regressions for non-fatal Telegram transport failures during preview flush and final delivery.
293
- - `[Extension Sections Draft]` Added a draft design note for pi-native Telegram extension sections, reserved the future `section:` callback prefix, linked the draft from docs, and recorded the project philosophy that `pi-telegram` should inherit π's extensibility model as a shared Telegram shell for loaded extensions. Impact: the future 0.10.0 extension platform direction is documented without exposing a stable API yet.
314
+ - `[Extension Sections Draft]` Added a draft design note for pi-native Telegram extension sections, reserved the future `section:` callback prefix, linked the draft from docs, and recorded the project philosophy that `pi-telegram` should inherit Pi's extensibility model as a shared Telegram shell for loaded extensions. Impact: the future 0.10.0 extension platform direction is documented without exposing a stable API yet.
294
315
  - `[Docs Formatting]` Normalized project Markdown so prose paragraphs stay as single logical lines and Markdown tables remain narrow instead of using artificial hard wraps. Impact: editors and viewers can handle visual wrapping naturally while fixed-width structures stay readable.
295
316
  - `[Settings Copy]` Tightened the proactive-push settings text by removing redundant persistence/default wording.
296
317
 
@@ -323,7 +344,7 @@
323
344
  ## 0.9.0: Hidden Settings And Proactive Push
324
345
 
325
346
  - `[Settings Menu]` Added hidden Telegram `/settings` with a proactive push checkbox detail submenu plus `/telegram-settings` in the terminal. Impact: operators can see green/black binary flag state, use green/black/yellow on/off checkbox controls from Telegram, and toggle the same proactive push flag locally without adding a visible bot-command entry.
326
- - `[Proactive Push]` `telegram.json` now supports `proactivePush`; when enabled, successful local non-Telegram π final replies are sent to the paired Telegram chat if no Telegram turn is active and the current session still owns the Telegram lock. Local prompt text stays private because the bot does not own or mirror terminal user messages. Impact: long local tasks can notify the phone with result context without leaking from stale bridge owners or failed/aborted turns.
347
+ - `[Proactive Push]` `telegram.json` now supports `proactivePush`; when enabled, successful local non-Telegram Pi final replies are sent to the paired Telegram chat if no Telegram turn is active and the current session still owns the Telegram lock. Local prompt text stays private because the bot does not own or mirror terminal user messages. Impact: long local tasks can notify the phone with result context without leaking from stale bridge owners or failed/aborted turns.
327
348
  - `[Queue UI]` Empty queue states now use the bottom-filled `⌛` hourglass while non-empty queue states keep `⏳`. Queue item details now show the selected queue position above the raw prompt preview, preserve reaction-specific priority emoji in the heading, and use side-by-side Priority/Normal tabs that refresh the heading marker immediately. The terminal status bar now stays yellow active while Telegram-owned work still has running tools even if a queued prompt is removed by reaction. Impact: queue emptiness has a small visual easter egg, item submenus stay oriented without changing queue semantics, and queue-removal reactions no longer visually degrade active work to connected.
328
349
  - `[Model Menu]` Model rows now open a detail submenu with Back, ☑️ Activate/🟢 Active selection, and yellow/black-marked Scoped/All membership tabs. Impact: model selection remains one tap away while scoped model membership can be managed from Telegram.
329
350
  - `[Status Menu]` The main Telegram menu status row now shows `compacting` while a Telegram `/compact` run is active. Impact: the phone UI reflects the same compaction state that already blocks dispatch and appears in terminal status.
@@ -331,7 +352,7 @@
331
352
 
332
353
  ## 0.8.2: Lock-Safe Delivery
333
354
 
334
- - `[Lock Safety]` Active Telegram turns now re-check singleton ownership before preview flushes and final agent-end delivery. Impact: an old π instance stays silent after another instance takes the Telegram bridge lock, even if the old instance finishes a long-running prompt later.
355
+ - `[Lock Safety]` Active Telegram turns now re-check singleton ownership before preview flushes and final agent-end delivery. Impact: an old Pi instance stays silent after another instance takes the Telegram bridge lock, even if the old instance finishes a long-running prompt later.
335
356
  - `[Inbound Handlers]` The first step of an inbound composition now receives the full configured handler timeout before elapsed-time accounting starts on later steps. Impact: composition timeout behavior is deterministic and avoids one-millisecond test/runtime drift at pipeline start.
336
357
  - `[Menu UI]` Model and Thinking submenu headers now include their matching command icons (`🤖` and `🧠`). Impact: submenu headings match the Queue menu's icon-led style.
337
358
 
@@ -340,7 +361,7 @@
340
361
  - `[Outbound Voice]` Composed voice handlers now pass the original `telegram_voice` text to the first pipeline step through stdin, then continue piping each step's stdout into the next step. Impact: translate-from-stdin voice pipelines can translate hidden voice text before TTS instead of failing with an empty first-step input.
341
362
  - `[Queue Menu]` Queue item detail previews now render prompt text inside a bounded raw `<pre>` block, and generic queue navigation/headings use the `⏳` waiting icon. Impact: absolute file paths and attachment references remain readable without Telegram interpreting slash-prefixed paths as commands, long previews are truncated below Telegram's message limit, and the queue surface has a clearer generic icon distinct from ordered-list or priority markers.
342
363
  - `[Queue Delete]` Queue item removal now uses explicit `🗑 Delete` wording and opens a two-button confirmation (`🗑 Yes, delete` / `❌ No`) before mutating the queue. Impact: accidental queue-item deletion is harder while the item detail flow remains compact.
343
- - `[Queue Priority]` Priority reactions now preserve the exact normalized promotion emoji and render it in both queue-menu rows and the π status-bar queued preview. Reaction metadata is grouped into semantic id ranges (`10..13` for priority, `20..23` for removal). Impact: `👍`, `⚡`, `❤️`, and `🕊️` keep the same priority semantics while making the user's chosen reaction visible across Telegram and TUI surfaces.
364
+ - `[Queue Priority]` Priority reactions now preserve the exact normalized promotion emoji and render it in both queue-menu rows and the Pi status-bar queued preview. Reaction metadata is grouped into semantic id ranges (`10..13` for priority, `20..23` for removal). Impact: `👍`, `⚡`, `❤️`, and `🕊️` keep the same priority semantics while making the user's chosen reaction visible across Telegram and TUI surfaces.
344
365
  - `[Configuration Docs]` Documented the configuration philosophy that rich visual/TUI setup stays minimal for now while agents can read README/docs and update `telegram.json` for advanced workflows. Impact: configuration guidance matches the extension's agent-assisted operator model without adding premature TUI surfaces.
345
366
  - `[Outbound Docs]` Tightened voice-handler critical-step wording around transform → TTS → conversion pipelines and handler-level fallbacks. Impact: docs now match translated voice pipelines without implying provider-specific TTS fallbacks.
346
367
  - `[Command Template Docs]` Updated `docs/command-templates.md` to the current portable standard. Impact: the documented standard now includes retry, fail-open composition, critical-step abort semantics, and the 30s default timeout without requiring cross-extension references.
@@ -348,8 +369,8 @@
348
369
 
349
370
  ## 0.8.0: Handler Bus
350
371
 
351
- - `[Inbound Handlers]` Added `inboundHandlers` as the provider-neutral Telegram → π transformation bus. Raw Telegram text can match `type: "text"`, `mime: "text/plain"`, or `mime: "text/*"`, receives text on stdin and `{text}`, and non-empty stdout replaces the prompt text before queueing; media/file handlers keep the existing `{file}`/`{mime}`/`{type}` behavior with optional independent selectors. Impact: translation, normalization, STT, OCR, and file extraction can share one command-template integration model.
352
- - `[Text Attachments]` Attached `text/plain`/`text/*` files now have a built-in fail-open reader that injects UTF-8 content into `[outputs]` when no configured handler produced output. Impact: ordinary `.txt` and other text documents become readable to π without custom extraction config.
372
+ - `[Inbound Handlers]` Added `inboundHandlers` as the provider-neutral Telegram → Pi transformation bus. Raw Telegram text can match `type: "text"`, `mime: "text/plain"`, or `mime: "text/*"`, receives text on stdin and `{text}`, and non-empty stdout replaces the prompt text before queueing; media/file handlers keep the existing `{file}`/`{mime}`/`{type}` behavior with optional independent selectors. Impact: translation, normalization, STT, OCR, and file extraction can share one command-template integration model.
373
+ - `[Text Attachments]` Attached `text/plain`/`text/*` files now have a built-in fail-open reader that injects UTF-8 content into `[outputs]` when no configured handler produced output. Impact: ordinary `.txt` and other text documents become readable to Pi without custom extraction config.
353
374
  - `[Inbound Domain]` Renamed the implementation module and mirrored regression suite from `attachment-handlers` to `inbound-handlers`. Impact: file names now match the unified text/media preprocessing domain while legacy `attachmentHandlers` config remains supported.
354
375
  - `[Outbound Attachment Domain]` Renamed the outbound file-delivery module and mirrored regression suite from `attachments` to `outbound-attachments`. Impact: `telegram_attach` ownership now reads as an outbound domain beside `outbound-handlers` while behavior stays unchanged.
355
376
  - `[Inbound Docs]` Consolidated the deprecated `docs/attachment-handlers.md` page into `docs/inbound-handlers.md` and removed the old page. Impact: the inbound bus docs are now the canonical home for legacy `attachmentHandlers`, placeholders, ordered fallbacks, and prompt-output behavior without split documentation.
@@ -359,22 +380,22 @@
359
380
 
360
381
  ## 0.7.2: Split Text Coalescing Hotfix
361
382
 
362
- - `[Text Coalescing]` Telegram text messages that look like automatic splits of one near-limit human message are now short-debounced and forwarded to π as one prompt, using a conservative 3600-character near-limit threshold. Commands, bot messages, media groups, captions, non-contiguous messages, and normal short follow-ups bypass coalescing. Impact: long pasted logs/prompts are less likely to arrive as separate π turns when Telegram chunks them.
383
+ - `[Text Coalescing]` Telegram text messages that look like automatic splits of one near-limit human message are now short-debounced and forwarded to Pi as one prompt, using a conservative 3600-character near-limit threshold. Commands, bot messages, media groups, captions, non-contiguous messages, and normal short follow-ups bypass coalescing. Impact: long pasted logs/prompts are less likely to arrive as separate Pi turns when Telegram chunks them.
363
384
  - `[Runtime Tests]` The media-group runtime regression now waits for the real debounce instead of mixing fake timers with the polling loop, and the reaction-priority runtime test flushes pending microtasks before ending the active turn. Impact: CI should stop failing on timing-only races around delayed dispatch and queued reaction mutations.
364
385
  - `[Callback Namespaces]` Current status-screen navigation callbacks now use the canonical `menu:` namespace (`menu:model`, `menu:thinking`, `menu:queue`). `status:` remains reserved as an owned legacy prefix but is no longer emitted by current UI. Impact: new inline menu callbacks align with the unified app-menu model while old `status:` payloads still cannot leak to external fallback handlers.
365
386
 
366
387
  ## 0.7.1: Layered Callback Interop
367
388
 
368
- - `[Callback Interop]` Unknown Telegram inline-button callback data that does not belong to pi-telegram-owned prefixes (`tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`) is now forwarded to π as `[callback] <data>` after assistant-button, queue-menu, and app-menu handlers decline it. `docs/callback-namespaces.md` defines the shared callback namespace standard for layered extensions. Impact: layered π extensions can namespace and handle their own Telegram inline buttons without polling the same bot or forking pi-telegram.
369
- - `[Prompt Templates]` Prompt-template aliases stay visible only inside `/start` and are no longer registered in the Telegram bot command menu. Impact: reusable π workflows remain discoverable without making Telegram's global command menu noisy.
389
+ - `[Callback Interop]` Unknown Telegram inline-button callback data that does not belong to pi-telegram-owned prefixes (`tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`) is now forwarded to Pi as `[callback] <data>` after assistant-button, queue-menu, and app-menu handlers decline it. `docs/callback-namespaces.md` defines the shared callback namespace standard for layered extensions. Impact: layered Pi extensions can namespace and handle their own Telegram inline buttons without polling the same bot or forking pi-telegram.
390
+ - `[Prompt Templates]` Prompt-template aliases stay visible only inside `/start` and are no longer registered in the Telegram bot command menu. Impact: reusable Pi workflows remain discoverable without making Telegram's global command menu noisy.
370
391
 
371
392
  ## 0.7.0: Unified App Menu & Command Template Hardening
372
393
 
373
- - `[Commands]` Visible Telegram bot command menu now exposes `/start`, `/compact`, `/next`, `/continue`, `/abort`, and `/stop`; `/help`, `/status`, `/model`, `/thinking`, and `/queue` remain hidden compatibility shortcuts. `/start`, `/help`, and `/status` open one unified app menu containing command help, status rows, and inline controls. Command emoji are centralized as fixed adornments in the commands domain and reused by matching menu buttons (`🤖` model, `🧠` thinking); `/next` uses `⏩` and `/continue` uses `▶️`. `/continue` enqueues a priority Telegram-owned `continue` prompt instead of forcing the next queued item or requiring π to be idle. Impact: the visible command surface is cleaner while existing operator muscle memory still works and skills can react to queued `continue` prompts.
394
+ - `[Commands]` Visible Telegram bot command menu now exposes `/start`, `/compact`, `/next`, `/continue`, `/abort`, and `/stop`; `/help`, `/status`, `/model`, `/thinking`, and `/queue` remain hidden compatibility shortcuts. `/start`, `/help`, and `/status` open one unified app menu containing command help, status rows, and inline controls. Command emoji are centralized as fixed adornments in the commands domain and reused by matching menu buttons (`🤖` model, `🧠` thinking); `/next` uses `⏩` and `/continue` uses `▶️`. `/continue` enqueues a priority Telegram-owned `continue` prompt instead of forcing the next queued item or requiring Pi to be idle. Impact: the visible command surface is cleaner while existing operator muscle memory still works and skills can react to queued `continue` prompts.
374
395
  - `[Application Menu]` `/start` opens command help plus status rows and the inline application menu; `/queue` opens the queue section directly, the status menu Queue button shows the current queued-item count, all submenus keep Back/Main menu navigation in the top row, and queued items are listed in dispatch order with numeric labels plus `⚡`/`📎` markers. Queue menu message text uses the same HTML heading style as the other inline menus; empty queue menus render bold message text with only the Main menu navigation button instead of a disabled empty-state button. Item submenus support Back, Priority/Normal tabs, and Cancel, and stale item clicks refresh the live list. Impact: queued Telegram work is inspectable and mutable from the menu control surface without relying only on reactions.
375
- - `[Prompt Templates]` `/start` now shows a separate block for π prompt-template commands, and the Telegram bot command menu registers Telegram-safe prompt-template aliases such as `fix-tests` → `/fix_tests` when they do not conflict with built-in bridge commands or hidden shortcuts. Sending `/template_name args` from Telegram expands the matching π prompt-template file before queueing the turn. Impact: reusable π workflows are available from Telegram without duplicating prompt text manually.
396
+ - `[Prompt Templates]` `/start` now shows a separate block for Pi prompt-template commands, and the Telegram bot command menu registers Telegram-safe prompt-template aliases such as `fix-tests` → `/fix_tests` when they do not conflict with built-in bridge commands or hidden shortcuts. Sending `/template_name args` from Telegram expands the matching Pi prompt-template file before queueing the turn. Impact: reusable Pi workflows are available from Telegram without duplicating prompt text manually.
376
397
  - `[Keyboard]` Shared Telegram inline-keyboard reply-markup structure was extracted to `keyboard`, while `menu` owns application-control button semantics and `outbound-handlers` owns assistant-authored button semantics. Impact: inline UI domains share one Bot API shape without centralizing feature behavior.
377
- - `[Domain DAG]` Source-module opening comments now include `Zones:` tags for cross-cutting responsibility areas such as Telegram transport, π agent lifecycle, TUI, and shared utilities. Impact: flat files keep folder-like orientation without adding directory nesting.
398
+ - `[Domain DAG]` Source-module opening comments now include `Zones:` tags for cross-cutting responsibility areas such as Telegram transport, Pi agent lifecycle, TUI, and shared utilities. Impact: flat files keep folder-like orientation without adding directory nesting.
378
399
  - `[Menu Refactor]` Queue-menu UI moved from `menu.ts` into the flat `menu-queue` domain while core queue mechanics remain in `queue`; model-menu state, scoped model pages, callback planning, and model-menu rendering moved into the flat `menu-model` domain while core model semantics remain in `model`; thinking-menu text, markup, callbacks, and rendering moved into the flat `menu-thinking` domain; status-menu payloads, callbacks, and rendering moved into the flat `menu-status` domain. Impact: `menu.ts` is smaller and queue/model/thinking/status control surfaces have dedicated UI boundaries without adding folders or changing Telegram behavior.
379
400
  - `[Menu]` Busy-state messages now mention `/abort`, `/next`, and `/stop`; submenu main navigation uses top-row `⬆️ Main menu`; thinking-menu text is a compact bold heading because the selected level is already marked in the buttons; model-menu scope and pagination controls now sit at the top under Main menu, and the pagination indicator opens a compact `<b>Choose a page:</b>` picker with numbered page buttons.
380
401
  - `[Queue Reactions]` Priority reactions now accept `👍`, `⚡️`, `❤️`, and `🕊`; removal reactions now accept `👎`, `👻`, `💔`, and `💩`. Impact: users can use more default Telegram reactions for queue control while keeping the same priority/removal semantics.