@llblab/pi-telegram 0.11.1 → 0.12.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 (45) hide show
  1. package/AGENTS.md +17 -12
  2. package/BACKLOG.md +0 -10
  3. package/CHANGELOG.md +33 -2
  4. package/README.md +42 -25
  5. package/api/inbound.ts +14 -0
  6. package/api/keyboard.ts +10 -0
  7. package/api/outbound.ts +11 -0
  8. package/api/sections.ts +17 -0
  9. package/api/updates.ts +11 -0
  10. package/api/voice.ts +24 -0
  11. package/docs/README.md +7 -5
  12. package/docs/architecture.md +176 -135
  13. package/docs/callback-namespaces.md +3 -3
  14. package/docs/command-templates.md +81 -24
  15. package/docs/{inbound-handlers.md → inbound.md} +13 -10
  16. package/docs/locks.md +3 -3
  17. package/docs/{outbound-handlers.md → outbound.md} +13 -10
  18. package/docs/public-api.md +266 -0
  19. package/docs/{extension-sections.md → sections.md} +31 -27
  20. package/docs/ui-style.md +165 -0
  21. package/docs/{external-handlers.md → updates.md} +33 -31
  22. package/docs/voice.md +17 -14
  23. package/index.ts +86 -261
  24. package/lib/bindings.ts +301 -0
  25. package/lib/command-templates.ts +163 -32
  26. package/lib/commands.ts +114 -1
  27. package/lib/config.ts +45 -4
  28. package/lib/{inbound-handlers.ts → inbound.ts} +5 -4
  29. package/lib/lifecycle.ts +122 -1
  30. package/lib/menu-model.ts +3 -3
  31. package/lib/menu-queue.ts +1 -1
  32. package/lib/menu-settings.ts +63 -32
  33. package/lib/menu-status.ts +1 -1
  34. package/lib/menu.ts +1 -1
  35. package/lib/{outbound-handlers.ts → outbound.ts} +21 -11
  36. package/lib/pi.ts +4 -0
  37. package/lib/polling.ts +4 -3
  38. package/lib/preview.ts +1 -1
  39. package/lib/routing.ts +45 -13
  40. package/lib/{extension-sections.ts → sections.ts} +37 -8
  41. package/lib/time-injection.ts +1 -1
  42. package/lib/updates.ts +121 -1
  43. package/lib/voice.ts +33 -14
  44. package/package.json +11 -1
  45. package/lib/external-handlers.ts +0 -166
package/AGENTS.md CHANGED
@@ -24,10 +24,12 @@
24
24
  ## 3. Project Topology
25
25
 
26
26
  - `/index.ts`: Main extension entrypoint and runtime composition layer for the bridge
27
- - `/lib/*.ts`: Flat domain modules for reusable runtime logic. Favor domain files such as queueing/runtime, replies, polling, updates, outbound-attachments, commands, lifecycle hooks, prompts, prompt-templates, pi SDK adapter, Telegram API, config, turns, media, setup, rendering, app menu, menu-model, menu-thinking, menu-queue, status/model-resolution support, and other cohesive bridge subsystems; use `shared` only when a type or constant truly spans multiple domains
28
- - `/tests/*.test.ts`: Domain-mirrored regression suites that follow the same flat naming as `/lib`
27
+ - `/api/*.ts`: Public package entrypoint membranes. Export only stable companion-extension symbols documented in `/docs/public-api.md`; keep runtime helpers in `/lib` package-private.
28
+ - `/lib/*.ts`: Flat domain modules for reusable runtime logic. Favor domain files such as queueing/runtime, replies, polling, updates, outbound-attachments, commands, lifecycle hooks, prompts, prompt-templates, pi SDK adapter/bindings, Telegram API, config, turns, media, setup, rendering, app menu, menu-model, menu-thinking, menu-queue, status/model-resolution support, and other cohesive bridge subsystems; use `shared` only when a type or constant truly spans multiple domains
29
+ - `/tests/*.test.ts`: Domain-mirrored regression suites that follow the same flat naming as `/lib` plus public package-boundary regressions for `/api`
29
30
  - `/docs/README.md`: Documentation index for technical project docs
30
31
  - `/docs/architecture.md`: Runtime and subsystem overview for the bridge
32
+ - `/docs/public-api.md`: Stable public API map for commands, config, assistant markup, extension APIs, package entrypoints, and compatibility boundaries
31
33
  - `/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.
32
34
  - `/AGENTS.md`: Durable engineering and runtime conventions
33
35
  - `/BACKLOG.md`: Canonical open work
@@ -73,7 +75,7 @@
73
75
  - 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
74
76
  - `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
75
77
  - 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
76
- - Optional `telegram.json` `time` may add `[time] YYYY-MM-DD HH:mm:ss <timezone>` to Telegram-originated prompts for wall-clock context. It is off by default, uses `time.injectionMode` values `off|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: off|always|interval` controls `time.injectionMode` only.
78
+ - 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.
77
79
  - 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
78
80
  - 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
79
81
  - `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
@@ -106,11 +108,11 @@
106
108
  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.
107
109
 
108
110
  - Scheduling and lifecycle: `queue`, `runtime`, `lifecycle`, `locks`
109
- - Telegram transport and inbound flow: `api`, `polling`, `updates`, `routing`, `media`, `turns`, `inbound-handlers`, `config`, `setup`
110
- - Response surfaces: `preview`, `replies`, `rendering`, `keyboard`, `outbound-attachments`, `outbound-handlers`, `voice`, `status`
111
+ - Telegram transport and inbound flow: `api`, `polling`, `updates`, `routing`, `media`, `turns`, `inbound`, `config`, `setup`
112
+ - Response surfaces: `preview`, `replies`, `rendering`, `keyboard`, `outbound-attachments`, `outbound`, `voice`, `status`
111
113
  - Controls and application menu UI: `commands`, `menu`, `menu-model`, `menu-thinking`, `menu-status`, `menu-queue`, `model`, `prompts`
112
- - Extension platform: `extension-sections` owns section registry, token mapping, callback dispatch, context building, and its globalThis bridge; `voice` owns the voice-provider registry and its globalThis bridge
113
- - Pi SDK boundary: `pi` owns direct pi imports and bound extension API ports
114
+ - 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
115
+ - 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
114
116
 
115
117
  ## 6.4 Entrypoint And Import Boundaries
116
118
 
@@ -130,6 +132,7 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
130
132
  - When fork identity changes, keep `README.md`, package metadata, and docs aligned so the published package does not point back at stale upstream coordinates
131
133
  - 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.
132
134
  - 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`.
135
+ - 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.
133
136
  - 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
134
137
 
135
138
  ## 8. Integration Protocols
@@ -141,11 +144,13 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
141
144
  - 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
142
145
  - Shared inline-keyboard structure belongs to `keyboard`; application-control button labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue` while core queue mechanics stay in `queue`
143
146
  - Telegram `/settings` options should open nested detail submenus by default: checkbox options show a description plus Back, `on`, and `off`; list options show Back plus selectable values. One-shot actions such as syncing may run directly without a submenu when there is no meaningful choice or description step.
147
+ - Inline UI labels and dialogs follow [`docs/ui-style.md`](./docs/ui-style.md): action buttons use emoji plus capitalized action text, state & navigation buttons show state and lead to a submenu, first-level submenus start with `⬆️ Main menu` while deeper submenus start with `⬆️ Back`, boolean toggles use Capitalized horizontal `On`/`Off` with green active `On`, yellow active `Off`, and black inactive dots, tabs use capitalized labels with purple default-state and yellow elevated-state active dots and black inactive dots, vertical option lists mark only the current value green, and confirmation dialogs use a single bold text-only question with emoji on buttons only.
144
148
  - Inbound text/media may be transformed through configured `inboundHandlers` before queueing; legacy `attachmentHandlers` are deprecated compatibility aliases appended after `inboundHandlers`; outbound files must flow through `telegram_attach`
145
149
  - 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
146
- - Public handler API matrix: use `registerTelegramInboundHandler(kind, handler)` for generic programmatic inbound transforms, `registerTelegramOutboundHandler(kind, handler)` for generic programmatic outbound transforms, `registerTelegramVoiceTranscriptionProvider()` for voice/audio STT providers, and `registerTelegramVoiceSynthesisProvider()` for TTS/voice-output providers. 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 external command execution is needed
150
+ - 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
151
+ - 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
147
152
  - 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
148
- - Unknown callback data not owned by pi-telegram prefixes (`tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `section:`, `settings:`) may be forwarded as `[callback] <data>` after built-in handlers decline it; external extensions should follow `docs/callback-namespaces.md` and must not poll the same bot independently
153
+ - 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
149
154
  - 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
150
155
  - 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
151
156
 
@@ -154,15 +159,15 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
154
159
  - `Section identity`: use the same identity-key rules as the Extension Locks Standard (`package.json/name` → canonical id); no separate `owner` field
155
160
  - `Token mapping`: Telegram's 64-byte `callback_data` limit forces compact numeric tokens (`section:0:action:payload`). Section authors never hand-roll `section:` strings — use `ctx.callbackData(action, payload?)`
156
161
  - `Navigation hierarchy`: Back buttons are auto-prepended by `ctx.edit()` / `ctx.open()`. Root views use `⬆️ Main menu` → `menu:back`. Nested views from `handleCallback` use `⬆️ Back` → `section:<token>:open`. Settings views use `⬆️ Back` → `settings:list`
157
- - `Context ports`: sections receive `TelegramSectionContext` / `TelegramSectionCallbackContext` with `answerCallback`, `edit`, `open`, `enqueuePrompt`, and `callbackData`. No filesystem access, no raw bot clients, no second poller
162
+ - `Context ports`: sections receive `TelegramSectionContext` / `TelegramSectionCallbackContext` with `answerCallback`, `edit`, `open`, `enqueuePrompt`, and `callbackData`. No filesystem access, no raw bot clients, no second polling loop
158
163
  - `Settings indicators`: use `settings.getLabel()` for dynamic status rows in the Settings submenu (e.g., `🟢`/`⚫️` based on internal state). Called on every Settings list render
159
164
  - `Handler fallback`: `section.handleCallback` runs first; if it returns `"pass"` and `settings.handleCallback` exists, the settings handler runs with a fresh context carrying `backCallback="settings:list"`
160
- - `Stale tokens`: unknown or unregistered tokens answer the callback with a short popup. Section errors are caught and surfaced as popup text — no unhandled exceptions leak to the poller
165
+ - `Stale tokens`: unknown or unregistered tokens answer the callback with a short popup. Section errors are caught and surfaced as popup text — no unhandled exceptions leak to polling
161
166
  - `Load order`: `pi-telegram` must load first (sets `globalThis.__piTelegramSectionRegistry__`). Consumer extensions load second. The typed import is the preferred path; the `globalThis` bridge exists for load-order tolerance
162
167
  - `Shutdown`: call `pi.on("shutdown", () => unregister())` in the extension's default export
163
168
  - `Section separators`: extension-injected main-menu rows appear before the **⚙️ Settings** row. Extension settings rows appear before built-in Proactive push controls
164
169
  - `Model button format`: use `provider/ModelId` format (e.g., `anthropic/claude-sonnet-4-5`) across model menu buttons and status row. The compact `provider/id` form is canonical
165
- - `Section domain ownership`: `lib/extension-sections.ts` owns the registry, token mapping, callback dispatch, and context building. `lib/menu.ts` dispatches `section:` callbacks before built-in handling. `lib/menu-status.ts` injects section rows. `lib/menu-settings.ts` injects settings rows and passes `sectionRegistry` through callback deps
170
+ - `Section domain ownership`: `lib/sections.ts` owns the registry, token mapping, callback dispatch, and context building. `lib/menu.ts` dispatches `section:` callbacks before built-in handling. `lib/menu-status.ts` injects section rows. `lib/menu-settings.ts` injects settings rows and passes `sectionRegistry` through callback deps
166
171
  - `Callback routing order`: button actions → queue menu → settings menu → section callbacks → built-in menu handling → `[callback]` fallback. Settings menu callbacks always pass `sectionRegistry` to `updateTelegramSettingsMenuMessage` and `handleTelegramSettingsMenuCallbackAction`
167
172
 
168
173
  ## 9. Pre-Task Preparation Protocol
package/BACKLOG.md CHANGED
@@ -1,13 +1,3 @@
1
1
  # Project Backlog
2
2
 
3
3
  ## Open Work
4
-
5
- - [ ] Adapt `pi-xai-voice` to the finalized voice-provider contract.
6
- - Priority: High after `0.11.0` lands.
7
- - Idea: Update provider imports to `@llblab/pi-telegram/lib/voice.ts`, persist `voice.replyMode` to the same `telegram.json` location pi-telegram reads, return `transcriptText` only when the provider's transcript toggle is enabled, and rely on provider-owned OGG/Opus conversion.
8
- - Exit: `pi-xai-voice` works against `pi-telegram@0.11.x` without direct `globalThis` access or fork-local import resolution.
9
-
10
- - [ ] Explore always-available outbound Telegram tools for queued artifacts and controls.
11
- - Priority: Low.
12
- - Idea: Provide tools such as `telegram_attach_file` and `telegram_attach_button` that can be called outside an active Telegram turn, using the paired chat/session as the delivery target when safe.
13
- - Exit: Design note defines active-turn versus ambient delivery semantics, safety constraints, failure modes, and whether the current `telegram_attach` contract should stay turn-scoped or gain an ambient companion.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Nothing yet.
6
+
7
+ ## 0.12.0: Public API Membranes, Telegram UX Safety, And Extension Interop
8
+
9
+ - `[Typing Status]` Auto-compaction now starts and stops the native Telegram `typing` keepalive like manual `/compact`, including timeout and shutdown cleanup. Active Telegram turns also re-arm typing on assistant message start/update events so transient provider/model errors cannot permanently leave a continuing run without Telegram activity feedback.
10
+ - `[Compaction Safety]` Telegram `/compact` now opens an inline confirmation dialog before manual compaction starts, protecting the operator from accidental taps near `/start`. The dialog follows the dedicated UI style guide: a bold text-only question with emoji only on the explicit `Yes, compact` and `No` buttons. Confirming edits the dialog directly to `Compaction started.` instead of showing a separate `Compaction confirmed.` step.
11
+ - `[Docs]` Added `docs/ui-style.md` as the focused style guide for inline buttons, toggles, tabs, option lists, cards, and dialogs.
12
+ - `[Docs]` Restructured `docs/architecture.md` into a clearer architectural map with runtime topology, domain ownership, core flows, extension surfaces, and operational behavior while pushing detailed UI/callback rules toward focused standards.
13
+ - `[Breaking API]` Bumped package version to `0.12.0` and renamed the public implementation domains to `lib/sections.ts`, `lib/updates.ts`, `lib/inbound.ts`, and `lib/outbound.ts`. Package exports now expose only the stable public API domains (`/sections`, `/updates`, `/inbound`, `/outbound`, `/voice`, `/keyboard`) and no longer expose the compatibility `./lib/*.ts` wildcard.
14
+ - `[Architecture]` Folded the public update-handler interop surface into `updates` and renamed the internal long-poll loop module back to `polling`, giving the pair concise one-word domains: `updates` for update contracts/classification/handler registry and `polling` for the `getUpdates` runtime. Entrypoint extraction now lives in `bindings`, a concrete pi-facing command/tool/lifecycle wiring boundary rather than a new product domain.
15
+ - `[Tests]` Added an architecture invariant and package self-import regressions that pin the `0.12.0` package exports to stable `/api` membranes with exact runtime export shapes and prevent accidental restoration of the removed `./lib/*.ts` wildcard.
16
+ - `[Docs]` Added `docs/public-api.md` as the public API map for commands, config, assistant markup, extension APIs, callback ownership, and public/internal stability boundaries.
17
+ - `[API]` Hardened Telegram Extension Sections by rejecting duplicate section ids and validating `ctx.callbackData()` against Telegram's 64-byte callback-data limit.
18
+ - `[API]` Added `registerTelegramUpdateHandler()` as the matrix-aligned low-level update bus name.
19
+ - `[API]` Documented the public low/high-level registration matrix: low-level buses (`updates`, `inbound`, `outbound`) intentionally have no ids, while high-level sections and voice providers use stable ids in new integrations.
20
+ - `[Settings UI]` Refined control conventions: boolean toggles use Capitalized horizontal `On`/`Off` with green active `On`, yellow active `Off`, and black inactive markers; state/navigation rows use Capitalized status values; horizontal tabs use capitalized labels with purple default-state and yellow elevated-state active markers; vertical option lists keep only the current value marked green; submenu navigation distinguishes `⬆️ Main menu` from deeper `⬆️ Back` rows; confirmation actions use explicit `Yes, ...` labels such as `🗜 Yes, compact`; added State & Navigation Buttons section to the style guide for buttons that show state and lead to a submenu.
21
+ - `[Config]` Time Injection Hidden mode now removes `time.injectionMode` from `telegram.json` instead of persisting `"hidden"`, matching Voice Reply Hidden semantics: missing key = default hidden state.
22
+
23
+ ## 0.11.2: Queue Continuation, Compaction Safety, And Settings Polish
24
+
25
+ - `[Time Context]` Renamed the disabled time injection mode from `off` to `hidden` in Settings and config defaults, matching voice reply mode semantics where no prompt-context line is injected. Legacy `off` callbacks/config values are still treated as hidden.
26
+ - `[Settings UI]` Settings detail headings now show the current value immediately after the bold label in monospace style, making each submenu title double as the active setting summary.
27
+ - `[Entrypoint]` Removed root-level API re-exports from `index.ts`; public extension APIs remain available from their owning `lib/*` modules while the root file stays a default-only composition root.
28
+ - `[Continue Queue]` `/continue` now enqueues a standalone priority prompt without preserving already queued prompts as history. Impact: queued prompts stay iterative queue items instead of being folded into one combined `continue` prompt.
29
+ - `[Auto Compaction]` Observes native `session_before_compact` / `session_compact` events and blocks queued Telegram prompt dispatch while compaction is running, then resumes the queue after compaction settles. Impact: queued Telegram turns no longer race Pi auto-compaction and trigger `Cannot read properties of undefined (reading 'signal')` after compaction.
30
+ - `[Command Templates]` Updated the command-template helper library and regressions with typed placeholders, array-index placeholder resolution, repeat fanout from array length, unbounded default timeout semantics, and trusted-command warnings.
31
+ - `[Docs]` Updated the command-template standard with typed args, array placeholders, failure propagation, recover cleanup, and trust-boundary guidance.
32
+ - `[Docs]` Decomposed oversized architecture documentation blocks into focused sections for runtime ownership, queue validation, application menu shape, outbound actions, and interactive controls. Impact: the architecture entry point is easier to scan without changing runtime behavior.
33
+ - `[README]` Split queue reaction shortcuts into priority and removal lists, added `pi-xai-voice` as a companion extension, and kept the time-injection row label compact while preserving the full detail-heading label. Impact: operator-facing docs match the current Telegram menu language.
34
+ - `[Context]` Recorded the convention that extension-local standards should stay self-contained and avoid naming sibling extension implementations as authorities. Impact: shared standards can evolve independently without creating false extension dependencies.
35
+
5
36
  ## 0.11.1: Time Context And Settings Polish
6
37
 
7
38
  - `[Time Context]` Added optional `telegram.json` `time` prompt context for Telegram-originated turns. `time.injectionMode` values are `off`, `always`, and per-chat `interval`; `time.interval` is stored in milliseconds and timezone comes from the system. The `[time]` line renders last after attachments, handler outputs, and voice context, and Settings exposes a `🕒 Time` mode selector.
@@ -138,7 +169,7 @@
138
169
  ## 0.9.4: Temp Dir And Command Template Hotfix
139
170
 
140
171
  - `[Telegram Temp Dir]` Default Telegram API temp files now respect `PI_CODING_AGENT_DIR`, falling back to `~/.pi/agent` when the env var is unset. Impact: sandboxed or relocated agent dirs no longer force Telegram downloads through the default home-directory path.
141
- - `[Command Templates]` Synced the local Command Template Standard with `pi-auto-tools@0.5.5`: command-template nodes now document `mode`, `label`, `delay`, `repeat`, parallel fanout semantics, zero-based repeat placeholders, padding, and limited arithmetic expressions such as `{_(index+1)}`. Impact: inbound/outbound Telegram handler docs and helpers share the current portable automation contract.
172
+ - `[Command Templates]` Updated the local Command Template Standard: command-template nodes now document `mode`, `label`, `delay`, `repeat`, parallel fanout semantics, zero-based repeat placeholders, padding, and limited arithmetic expressions such as `{_(index+1)}`. Impact: inbound/outbound Telegram handler docs and helpers share the current portable automation contract without depending on another extension's documentation.
142
173
  - `[Queue Menu]` Empty queue refresh clicks now rotate through compact alternate empty-state headings while preserving the default first-open `⌛ Queue is empty.` state, and the Refresh button now stays directly under Back for both empty and populated queue lists. Impact: manual queue polling feels alive and the primary refresh control stays in a stable location without changing queue semantics.
143
174
  - `[Package]` Bumped package metadata to `0.9.4` and kept the lockfile in sync.
144
175
 
@@ -190,7 +221,7 @@
190
221
  - `[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.
191
222
  - `[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.
192
223
  - `[Package]` Bumped package metadata to `0.8.1` and kept the lockfile in sync.
193
- - `[Command Template Docs]` Synchronized `docs/command-templates.md` bit-for-bit with the current portable standard shared by `pi-auto-tools`. Impact: the documented standard now includes retry, fail-open composition, critical-step abort semantics, and the 30s default timeout in the same wording across both extensions.
224
+ - `[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.
194
225
  - `[Lock Docs]` Synchronized `docs/locks.md` bit-for-bit with the extension-neutral Locks Standard shared by `pi-wakeup`. Impact: singleton ownership documentation no longer carries project-specific examples that prevent exact reuse across extensions.
195
226
 
196
227
  ## 0.8.0: Handler Bus
package/README.md CHANGED
@@ -85,7 +85,7 @@ What it feels like:
85
85
  Use these inside the Telegram DM with your bot. The main entrypoint is `/start`: it opens the operator menu and exposes many of the important agent controls that normally live in the CLI, adapted for Telegram.
86
86
 
87
87
  - **`/start`**: Pair the first Telegram user when needed, register bot commands, and open the inline application menu with command help, prompt-template commands, status rows, model controls, thinking controls, settings, and queue controls.
88
- - **`/compact`**: Start session compaction when the session is idle; Telegram shows the native typing indicator while compaction is running.
88
+ - **`/compact`**: Ask for inline confirmation, then start session compaction when the session is idle; Telegram shows the native typing indicator while manual or automatic compaction is running.
89
89
  - **`/next`**: Dispatch the next queued turn, aborting π first if needed.
90
90
  - **`/continue`**: Enqueue a priority `continue` prompt.
91
91
  - **`/abort`**: Abort the active run without touching the queue.
@@ -120,7 +120,10 @@ The inline application menu is the primary operator surface. It exposes status,
120
120
 
121
121
  Messages sent while π is busy enter the prompt queue and are processed in order. Control actions and model-switch continuation turns use higher-priority lanes so operational commands can resume before normal prompts.
122
122
 
123
- The menu is the primary way to inspect and mutate the queue. Reactions are an extra shortcut when Telegram delivers `message_reaction` updates for the chat: `👍`, `⚡️`, `❤️`, `🕊`, and `🔥` promote waiting work; `👎`, `👻`, `💔`, `💩`, and `🗑` remove it. The same rules apply to text, voice, files, images, and media groups.
123
+ The menu is the primary way to inspect and mutate the queue. Reactions are an extra shortcut when Telegram delivers `message_reaction` updates for the chat. The same rules apply to text, voice, files, images, and media groups:
124
+
125
+ - Priority shortcuts: `👍`, `⚡️`, `❤️`, `🕊`, and `🔥` promote waiting work.
126
+ - Removal shortcuts: `👎`, `👻`, `💔`, `💩`, and `🗑` remove waiting work from the queue.
124
127
 
125
128
  ### Streaming and Telegram HTML rendering
126
129
 
@@ -196,24 +199,30 @@ If `telegram.json` explicitly sets a valid `voice.replyMode`, prompts include co
196
199
 
197
200
  In `mirror` and `always` modes, the bridge transparently intercepts agent text responses and routes them through the outbound voice pipeline. Configured `outboundHandlers` with `type: "voice"` run first in their configured order; zero-config registered synthesis providers run after them as progressive fallbacks. If several synthesis providers are installed, they are tried in registration order and the first one that returns a valid `.ogg`/`.opus` artifact handles the reply; `undefined`, errors, or invalid output fall through to the next provider. If every voice generator fails, the bridge falls back to sending the text reply instead.
198
201
 
199
- Voice synthesis provider extensions (e.g. `pi-xai-voice`) register a TTS backend at runtime:
202
+ Voice synthesis provider extensions (e.g. `pi-xai-voice`) register a TTS backend at runtime through public API domain subpaths:
200
203
 
201
204
  ```typescript
202
- import { registerTelegramVoiceSynthesisProvider } from "@llblab/pi-telegram/lib/voice.ts";
203
- import { recordTelegramRuntimeEvent } from "@llblab/pi-telegram/lib/outbound-handlers.ts";
205
+ import { registerTelegramVoiceSynthesisProvider } from "@llblab/pi-telegram/voice";
206
+ import { recordTelegramRuntimeEvent } from "@llblab/pi-telegram/outbound";
204
207
 
205
208
  // Return path only (backward compatible)
206
- const dispose = registerTelegramVoiceSynthesisProvider(async (text, { lang, rate }) => {
207
- const path = await myTTS(text, { language: lang });
208
- return path; // must be .ogg or .opus
209
- });
209
+ const dispose = registerTelegramVoiceSynthesisProvider(
210
+ async (text, { lang, rate }) => {
211
+ const path = await myTTS(text, { language: lang });
212
+ return path; // must be .ogg or .opus
213
+ },
214
+ { id: "pi-xai-voice/tts" },
215
+ );
210
216
 
211
217
  // Return path + transcript caption
212
- const dispose2 = registerTelegramVoiceSynthesisProvider(async (text, { lang, rate }) => {
213
- const rewritten = rewriteWithSpeechTags(text); // internal TTS optimization
214
- const path = await myTTS(rewritten, { language: lang });
215
- return { audioPath: path, transcriptText: text };
216
- });
218
+ const dispose2 = registerTelegramVoiceSynthesisProvider(
219
+ async (text, { lang, rate }) => {
220
+ const rewritten = rewriteWithSpeechTags(text); // internal TTS optimization
221
+ const path = await myTTS(rewritten, { language: lang });
222
+ return { audioPath: path, transcriptText: text };
223
+ },
224
+ { id: "pi-xai-voice/tts-with-transcript" },
225
+ );
217
226
 
218
227
  // Surface diagnostics in /telegram-status
219
228
  recordTelegramRuntimeEvent("xai-voice", new Error("TTS complete"), {
@@ -222,17 +231,17 @@ recordTelegramRuntimeEvent("xai-voice", new Error("TTS complete"), {
222
231
  });
223
232
  ```
224
233
 
225
- Multiple synthesis providers can be registered; the bridge tries configured `type: "voice"` handlers first, then programmatic handlers, then registered synthesis providers in registration order until one succeeds. Providers and handlers receive the text to synthesize and optional `lang`/`rate` hints from `<!-- telegram_voice -->` markup or the automatic interception path. Voice delivery must produce `.ogg` or `.opus` files.
234
+ Multiple synthesis providers can be registered; stable provider registrations pass a durable `id`, while omitted ids remain a compatibility path for older providers. The bridge tries configured `type: "voice"` handlers first, then programmatic handlers, then registered synthesis providers in registration order until one succeeds. Providers and handlers receive the text to synthesize and optional `lang`/`rate` hints from `<!-- telegram_voice -->` markup or the automatic interception path. Voice delivery must produce `.ogg` or `.opus` files.
226
235
 
227
236
  ### Extension interop
228
237
 
229
- Unknown inline-button callbacks are forwarded to π as `[callback] <data>` when they do not belong to pi-telegram, so other extensions can namespace and handle Telegram buttons without polling the bot themselves. Layered extensions that need synchronous update handling can register a runtime interceptor on the shared update registry.
238
+ Unknown inline-button callbacks are forwarded to π as `[callback] <data>` when they do not belong to pi-telegram, so other extensions can namespace and handle Telegram buttons without polling the bot themselves. Layered extensions that need synchronous update handling can register a handler on the shared update registry.
230
239
 
231
240
  ### Extension Sections
232
241
 
233
- Ordinary pi extensions can register structured UI sections that appear in the main Telegram menu and Settings submenu without owning a second poller. Each section gets a narrow typed context with `edit`, `open`, `enqueuePrompt`, `answerCallback`, and `callbackData()` — enough to build interactive Telegram-native surfaces while `pi-telegram` owns transport, callback routing, navigation hierarchy, and diagnostics.
242
+ Ordinary pi extensions can register structured UI sections that appear in the main Telegram menu and Settings submenu without owning a second polling loop. Each section gets a narrow typed context with `edit`, `open`, `enqueuePrompt`, `answerCallback`, and `callbackData()` — enough to build interactive Telegram-native surfaces while `pi-telegram` owns transport, callback routing, navigation hierarchy, and diagnostics.
234
243
 
235
- Import `registerTelegramSection()` from `@llblab/pi-telegram/lib/extension-sections.ts` and return a disposer on shutdown. Sections can send interactive messages directly into the chat via `ctx.open()` — confirmation dialogs, approve/deny gates, and multi-step forms live outside the menu hierarchy while callbacks route through the same typed handler. See [`@llblab/pi-telegram-extension-demo`](https://github.com/llblab/pi-telegram-extension-demo) for a working reference and the [Extension Sections Standard](./docs/extension-sections.md) for the full contract.
244
+ Import `registerTelegramSection()` from `@llblab/pi-telegram/sections` and return a disposer on shutdown. Sections can send interactive messages directly into the chat via `ctx.open()` — confirmation dialogs, approve/deny gates, and multi-step forms live outside the menu hierarchy while callbacks route through the same typed handler. See [`@llblab/pi-telegram-extension-demo`](https://github.com/llblab/pi-telegram-extension-demo) for a working reference and the [Extension Sections Standard](./docs/sections.md) for the full contract.
236
245
 
237
246
  ### Proactive push
238
247
 
@@ -240,7 +249,7 @@ Import `registerTelegramSection()` from `@llblab/pi-telegram/lib/extension-secti
240
249
 
241
250
  ### Time context
242
251
 
243
- `telegram.json` can opt into a compact `[time]` line in Telegram-originated prompts so π has a wall-clock reference for requests such as "today", "now", or scheduling. It is off by default and uses the system timezone; the mode can also be changed from Settings → `🕒 Time`.
252
+ `telegram.json` can opt into a compact `[time]` line in Telegram-originated prompts so π has a wall-clock reference for requests such as "today", "now", or scheduling. It is hidden by default and uses the system timezone; the mode can also be changed from Settings → `🕒 Time injection`.
244
253
 
245
254
  ```json
246
255
  {
@@ -251,7 +260,7 @@ Import `registerTelegramSection()` from `@llblab/pi-telegram/lib/extension-secti
251
260
  }
252
261
  ```
253
262
 
254
- Modes are `off`, `always`, and `interval`. `interval` is measured in milliseconds and rate-limits the time line per chat in memory, so back-to-back messages do not repeatedly spend context on the same timestamp. When present, `[time]` is the final prompt-context section after attachments, handler outputs, and voice policy.
263
+ Modes are `hidden`, `always`, and `interval`. `hidden` means no time line is added to prompt context. `interval` is measured in milliseconds and rate-limits the time line per chat in memory, so back-to-back messages do not repeatedly spend context on the same timestamp. When present, `[time]` is the final prompt-context section after attachments, handler outputs, and voice policy.
255
264
 
256
265
  ## Docs
257
266
 
@@ -260,14 +269,16 @@ Modes are `off`, `always`, and `interval`. `interval` is measured in millisecond
260
269
  - [Changelog](./CHANGELOG.md): completed delivery history.
261
270
  - [Documentation Index](./docs/README.md): technical docs hub.
262
271
  - [Architecture](./docs/architecture.md): runtime and subsystem overview.
263
- - [Inbound Handlers](./docs/inbound-handlers.md): Telegram π preprocessing.
264
- - [Outbound Handlers](./docs/outbound-handlers.md): final text, voice, and artifact pipelines.
265
- - [Voice Integration](./docs/voice.md): voice reply policies, transparent interception, and provider extension API.
272
+ - [Public API](./docs/public-api.md): stable commands, config, package entrypoints, assistant markup, and extension APIs.
273
+ - [Inbound Handlers](./docs/inbound.md): Telegram π preprocessing.
274
+ - [Outbound Handlers](./docs/outbound.md): final text, voice, and artifact pipelines.
266
275
  - [Command Templates](./docs/command-templates.md): portable command-template contract.
267
276
  - [Callback Namespaces](./docs/callback-namespaces.md): callback interop for layered extensions.
268
- - [External Handlers](./docs/external-handlers.md): shared update interception.
269
- - [Extension Sections](./docs/extension-sections.md): Telegram extension sections platform for loading extensions that register UI surfaces.
277
+ - [Updates](./docs/updates.md): shared update interception.
278
+ - [Extension Sections](./docs/sections.md): Telegram extension sections platform for loading extensions that register UI surfaces.
279
+ - [Voice Integration](./docs/voice.md): voice reply policies, transparent interception, and provider extension API.
270
280
  - [Locks](./docs/locks.md): singleton polling ownership.
281
+ - [UI Style](./docs/ui-style.md): inline button, toggle, tab, option-list, card, and dialog style guide.
271
282
 
272
283
  ## Notes
273
284
 
@@ -285,6 +296,12 @@ Third-party extensions that integrate with `pi-telegram`:
285
296
  pi install npm:pi-telegram-tool-status
286
297
  ```
287
298
 
299
+ - [`pi-xai-voice`](https://github.com/luxus/pi-xai-voice) — Companion extension that adds xAI-powered TTS for voice reply policies and `telegram_voice` markup.
300
+
301
+ ```bash
302
+ pi install npm:pi-xai-voice
303
+ ```
304
+
288
305
  ## License
289
306
 
290
307
  MIT
package/api/inbound.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Public Telegram inbound API
3
+ * Zones: package boundary, companion extension interop
4
+ * Exposes the stable programmatic inbound handler surface while keeping handler runtime internals package-private
5
+ */
6
+
7
+ export {
8
+ registerTelegramInboundHandler,
9
+ type TelegramInboundHandlerFile,
10
+ type TelegramInboundHandlerOutput,
11
+ type TelegramInboundProgrammaticHandler,
12
+ type TelegramInboundProgrammaticHandlerInput,
13
+ type TelegramInboundProgrammaticHandlerResult,
14
+ } from "../lib/inbound.ts";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Public Telegram keyboard API
3
+ * Zones: package boundary, companion extension interop
4
+ * Exposes shared inline-keyboard structural types without runtime exports
5
+ */
6
+
7
+ export type {
8
+ TelegramInlineKeyboardButton,
9
+ TelegramInlineKeyboardMarkup,
10
+ } from "../lib/keyboard.ts";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Public Telegram outbound API
3
+ * Zones: package boundary, companion extension interop
4
+ * Exposes stable outbound handler and diagnostics surfaces while keeping delivery internals package-private
5
+ */
6
+
7
+ export {
8
+ recordTelegramRuntimeEvent,
9
+ registerTelegramOutboundHandler,
10
+ type TelegramOutboundProgrammaticHandler,
11
+ } from "../lib/outbound.ts";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Public Telegram sections API
3
+ * Zones: package boundary, companion extension interop
4
+ * Exposes the stable managed Telegram menu-section surface while keeping registry internals package-private
5
+ */
6
+
7
+ export {
8
+ getTelegramSectionDiagnostics,
9
+ registerTelegramSection,
10
+ type TelegramSectionCallbackContext,
11
+ type TelegramSectionCallbackResult,
12
+ type TelegramSectionContext,
13
+ type TelegramSectionDiagnostic,
14
+ type TelegramSectionRegistration,
15
+ type TelegramSectionSettingsRegistration,
16
+ type TelegramSectionView,
17
+ } from "../lib/sections.ts";
package/api/updates.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Public Telegram updates API
3
+ * Zones: package boundary, companion extension interop
4
+ * Exposes the stable raw-update handler surface while keeping update routing internals package-private
5
+ */
6
+
7
+ export {
8
+ registerTelegramUpdateHandler,
9
+ type TelegramUpdateHandler,
10
+ type TelegramUpdateHandlerVerdict,
11
+ } from "../lib/updates.ts";
package/api/voice.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Public Telegram voice API
3
+ * Zones: package boundary, companion extension interop
4
+ * Exposes the stable STT/TTS provider surface and voice policy helpers
5
+ */
6
+
7
+ export {
8
+ TELEGRAM_VOICE_REPLY_MODES,
9
+ computeVoicePromptContribution,
10
+ computeVoiceTurnFlags,
11
+ getTelegramVoiceReplyMode,
12
+ getTelegramVoiceSendTranscript,
13
+ isVoiceTurn,
14
+ registerTelegramVoiceSynthesisProvider,
15
+ registerTelegramVoiceTranscriptionProvider,
16
+ shouldSuppressPreviewForVoice,
17
+ type TelegramVoiceReplyMode,
18
+ type TelegramVoiceSynthesisProvider,
19
+ type TelegramVoiceSynthesisProviderResult,
20
+ type TelegramVoiceTranscriptionFile,
21
+ type TelegramVoiceTranscriptionProvider,
22
+ type TelegramVoiceTranscriptionProviderResult,
23
+ type TelegramVoiceTurnView,
24
+ } from "../lib/voice.ts";
package/docs/README.md CHANGED
@@ -5,11 +5,13 @@ Living index of project documentation in `/docs`.
5
5
  ## Documents
6
6
 
7
7
  - [architecture.md](./architecture.md) — Overview of the Telegram bridge runtime, queueing model, rendering pipeline, and interactive controls
8
+ - [public-api.md](./public-api.md) — Stable public API map: package entrypoints, commands, config, assistant markup, extension APIs, and compatibility boundaries
8
9
  - [command-templates.md](./command-templates.md) — Portable command-template standard core
9
- - [inbound-handlers.md](./inbound-handlers.md) — Local `pi-telegram` inbound text/media handler bus, programmatic inbound handlers, registered STT provider fallbacks, legacy `attachmentHandlers` compatibility, placeholders, and fallbacks
10
- - [outbound-handlers.md](./outbound-handlers.md) — Local `pi-telegram` outbound-handler config, text/voice/button behavior, voice synthesis provider fallback priority, artifact outputs, and callback routing
10
+ - [inbound.md](./inbound.md) — Local `pi-telegram` inbound text/media handler bus, programmatic inbound handlers, registered STT provider fallbacks, legacy `attachmentHandlers` compatibility, placeholders, and fallbacks
11
+ - [outbound.md](./outbound.md) — Local `pi-telegram` outbound-handler config, text/voice/button behavior, voice synthesis provider fallback priority, artifact outputs, and callback routing
12
+ - [callback-namespaces.md](./callback-namespaces.md) — Shared Telegram `callback_data` namespace standard for layered extensions
13
+ - [updates.md](./updates.md) — Update classification and runtime handler registry that lets layered extensions observe and consume Telegram updates without owning their own polling connection
14
+ - [sections.md](./sections.md) — Telegram Extension Sections Standard: registration contract, context ports, callback routing, navigation hierarchy, and demo reference for pi extensions that want Telegram UI surfaces
11
15
  - [voice.md](./voice.md) — Voice integration guide: detection, reply policy, STT/TTS provider registration, provider-owned conversion, and transparent interception
12
16
  - [locks.md](./locks.md) — Shared `locks.json` standard for singleton extension ownership
13
- - [callback-namespaces.md](./callback-namespaces.md) — Shared Telegram `callback_data` namespace standard for layered extensions
14
- - [external-handlers.md](./external-handlers.md) — Runtime interceptor registry that lets layered extensions observe and consume Telegram updates without owning their own polling connection
15
- - [extension-sections.md](./extension-sections.md) — Telegram Extension Sections Standard: registration contract, context ports, callback routing, navigation hierarchy, and demo reference for pi extensions that want Telegram UI surfaces
17
+ - [ui-style.md](./ui-style.md) — Inline UI style guide for buttons, toggles, tabs, option lists, cards, and dialogs