@llblab/pi-telegram 0.11.2 → 0.13.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 (53) hide show
  1. package/AGENTS.md +20 -15
  2. package/BACKLOG.md +1 -11
  3. package/CHANGELOG.md +41 -1
  4. package/README.md +15 -41
  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 +162 -226
  13. package/docs/callback-namespaces.md +3 -3
  14. package/docs/command-templates.md +18 -16
  15. package/docs/{inbound-handlers.md → inbound.md} +14 -11
  16. package/docs/locks.md +3 -3
  17. package/docs/{outbound-handlers.md → outbound.md} +14 -11
  18. package/docs/public-api.md +420 -0
  19. package/docs/{extension-sections.md → sections.md} +34 -30
  20. package/docs/ui-style.md +165 -0
  21. package/docs/{external-handlers.md → updates.md} +33 -31
  22. package/docs/voice.md +27 -19
  23. package/index.ts +88 -242
  24. package/lib/bindings.ts +299 -0
  25. package/lib/command-templates.ts +249 -60
  26. package/lib/commands.ts +114 -1
  27. package/lib/config.ts +44 -4
  28. package/lib/{inbound-handlers.ts → inbound.ts} +31 -21
  29. package/lib/lifecycle.ts +41 -6
  30. package/lib/locks.ts +4 -1
  31. package/lib/menu-model.ts +3 -3
  32. package/lib/menu-queue.ts +1 -1
  33. package/lib/menu-settings.ts +21 -10
  34. package/lib/menu-status.ts +1 -1
  35. package/lib/menu.ts +1 -1
  36. package/lib/outbound-buttons.ts +226 -0
  37. package/lib/outbound-markup.ts +357 -0
  38. package/lib/outbound-voice.ts +263 -0
  39. package/lib/outbound.ts +908 -0
  40. package/lib/polling.ts +4 -3
  41. package/lib/preview.ts +2 -2
  42. package/lib/queue.ts +3 -0
  43. package/lib/replies.ts +4 -1
  44. package/lib/routing.ts +44 -3
  45. package/lib/{extension-sections.ts → sections.ts} +37 -8
  46. package/lib/status.ts +13 -0
  47. package/lib/{api.ts → telegram-api.ts} +4 -4
  48. package/lib/text-groups.ts +3 -2
  49. package/lib/updates.ts +121 -1
  50. package/lib/voice.ts +67 -21
  51. package/package.json +13 -3
  52. package/lib/external-handlers.ts +0 -166
  53. package/lib/outbound-handlers.ts +0 -1663
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
@@ -95,7 +97,7 @@
95
97
 
96
98
  - Keep comments and user-facing docs in English unless the surrounding file already follows another convention
97
99
  - 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
98
- - Name extracted `/lib` modules and mirrored `/tests` suites by bare domain when the repository already supplies the Telegram scope; prefer `api.ts`, `queue.ts`, `updates.ts`, and `queue.test.ts` over redundant `telegram-*` filename prefixes
100
+ - 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
99
101
  - 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
100
102
  - 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
101
103
  - 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
@@ -106,18 +108,18 @@
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-markup`, `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
 
117
119
  - Keep preview appearance logic in the rendering domain and preview transport/lifecycle logic in the preview domain so richer streaming strategies can evolve without entangling Telegram delivery state with Markdown formatting rules
118
120
  - 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
119
121
  - 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
120
- - Keep the local `index.ts` plus `/lib/*.ts` import graph acyclic; `tests/invariants.test.ts` guards this boundary plus shared-bucket bans, empty interface-extension shell regressions, pi SDK centralization, source-only entrypoint Node-runtime/local-adapter/process/direct-pi access avoidance, runtime-domain isolation, structural leaf-domain import isolation, menu/model boundary drift, API/config default coupling, structural update/media coupling to API transport shapes, and attachment coupling to queue/inbound media/API helpers as domains keep evolving
122
+ - Keep the local `index.ts` plus `/lib/*.ts` import graph acyclic; `tests/invariants.test.ts` guards this boundary plus shared-bucket bans, empty interface-extension shell regressions, pi SDK centralization, source-only entrypoint Node-runtime/local-adapter/process/direct-pi access avoidance, runtime-domain isolation, structural leaf-domain import isolation, menu/model boundary drift, Telegram API/config default coupling, structural update/media coupling to Telegram API transport shapes, and attachment coupling to queue/inbound media/Telegram API helpers as domains keep evolving
121
123
  - Do not reintroduce shared bucket domains such as `lib/constants.ts`, `lib/types.ts`, `lib/globals.ts`, or broad global-augmentation files; constants, registry keys, state interfaces, and concrete transport shapes should stay in their owning domains, and `index.ts` should not grow new shared magic constants
122
124
  - Keep remaining `index.ts` code focused on cross-domain adapter wiring that needs live extension state, pi callbacks, Telegram API ports, or status updates; do not extract one-off closures solely to reduce line count
123
125
  - Domain-specific queue planning, preview transport/controller behavior, rendering, Telegram API transport, menu state, and command behavior should stay in their owning domains instead of moving to `/lib/runtime.ts` solely to shrink `index.ts`
@@ -142,29 +144,32 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
142
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
143
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`
144
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.
145
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`
146
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
147
- - 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
148
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
149
- - 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
150
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
151
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
156
+ - Abstract pi-telegram docs and README examples must not leak real companion-extension identities into generic provider ids, runtime-event categories, or sample code. Use neutral ids such as `@scope/voice-provider/tts` and reserve real companion names for explicit companion-extension lists or case-study references.
152
157
 
153
158
  ## 9. Extension Sections Conventions
154
159
 
155
160
  - `Section identity`: use the same identity-key rules as the Extension Locks Standard (`package.json/name` → canonical id); no separate `owner` field
156
161
  - `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?)`
157
- - `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`
158
- - `Context ports`: sections receive `TelegramSectionContext` / `TelegramSectionCallbackContext` with `answerCallback`, `edit`, `open`, `enqueuePrompt`, and `callbackData`. No filesystem access, no raw bot clients, no second poller
162
+ - `Navigation hierarchy`: Back buttons are auto-prepended by `ctx.edit()` only. Root views use `⬆️ Main menu` → `menu:back`. Nested views from `handleCallback` use `⬆️ Back` → `section:<token>:open`. Settings views use `⬆️ Back` → `settings:list`. `ctx.open()` sends standalone chat messages without auto-navigation
163
+ - `Context ports`: sections receive `TelegramSectionContext` / `TelegramSectionCallbackContext` with `answerCallback`, `edit`, `open`, `enqueuePrompt`, and `callbackData`. No filesystem access, no raw bot clients, no second polling loop
159
164
  - `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
160
165
  - `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"`
161
- - `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
166
+ - `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
162
167
  - `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
163
168
  - `Shutdown`: call `pi.on("shutdown", () => unregister())` in the extension's default export
164
169
  - `Section separators`: extension-injected main-menu rows appear before the **⚙️ Settings** row. Extension settings rows appear before built-in Proactive push controls
165
170
  - `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
166
- - `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
167
- - `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`
171
+ - `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
172
+ - `Callback routing order`: button actions → compact confirmations → queue menu → settings menu → section callbacks → built-in menu handling → `[callback]` fallback. Settings menu callbacks always pass `sectionRegistry` to `updateTelegramSettingsMenuMessage` and `handleTelegramSettingsMenuCallbackAction`
168
173
 
169
174
  ## 9. Pre-Task Preparation Protocol
170
175
 
package/BACKLOG.md CHANGED
@@ -1,13 +1,3 @@
1
1
  # Project Backlog
2
2
 
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.
3
+ No open work.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,46 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.13.0: Command Template Standard, Voice Hardening, And Domain Cleanup (2026-05-22)
4
+
5
+ - `[Architecture]` Extracted outbound assistant-action markup parsing into `lib/outbound-markup.ts` and removed the temporary Voice/Outbound/Queue import-cycle allowance. Impact: project source imports are fully acyclic again while preserving existing voice and outbound helper exports.
6
+ - `[Tests]` Updated the Pi SDK centralization invariant to guard the current `@earendil-works/*` package scope as well as the legacy scope. Impact: new direct SDK imports outside `lib/pi.ts` are caught again.
7
+ - `[Voice Providers]` Generated compatibility ids for STT/TTS providers now use monotonic counters with registry probing, and provider disposers now remove only their own registered instance. Impact: anonymous ids stay collision-safe across stale registry entries/module reloads, and stale disposers cannot delete replacement providers.
8
+ - `[Security]` Telegram temp directories, downloaded inbound files, and `locks.json` writes now use explicit private filesystem modes. Impact: private attachments and local polling ownership metadata are less exposed on permissive-umask or shared hosts.
9
+ - `[Docs]` Clarified that `voice.sendTranscript` is the bridge-owned transcript preference and updated provider examples to gate `transcriptText` with `getTelegramVoiceSendTranscript(config)`. Impact: companion voice providers no longer need to invent duplicate transcript/reply-policy UI.
10
+ - `[Tests]` Added a long-session runtime regression that preserves queued work across `/abort`, explicit `/next`, and in-flight model switching. Impact: the high-risk queue/dispatch path now has coverage for an operator session that chains abort recovery into model-switch continuation.
11
+ - `[Naming]` Renamed the concrete Bot API transport domain from `lib/api.ts` / `tests/api.test.ts` to `lib/telegram-api.ts` / `tests/telegram-api.test.ts`. Impact: the transport owner is clearer and no longer competes conceptually with the public `/api/*.ts` package membranes.
12
+ - `[Status]` `/telegram-status` recent runtime events now include a category summary before the detailed event list. Impact: provider, handler, and transport failure categories are easier to scan during companion-extension diagnostics.
13
+ - `[Tests]` Added a combined `telegram_voice` + `telegram_button` outbound planner smoke regression and corrected the text-group non-contiguous-tail fixture to reflect the widened split-message id gap. Impact: the rendering/markup watchlist now has direct coverage for mixed assistant action markup without changing runtime behavior.
14
+ - `[Audit]` Added fixture lifecycle coverage for STT/TTS provider re-registration across session-start/resume/reload-style boundaries, and scanned the local workspace for removed `@llblab/pi-telegram/lib/*` companion imports. Impact: the remaining hotfix lifecycle and migration audit items are now closed in this environment.
15
+ - `[Refactor]` Split assistant-authored button planning, callback storage, callback handling, and button prompt-turn construction from `lib/outbound.ts` into `lib/outbound-buttons.ts`, with symmetric `tests/outbound-buttons.test.ts` coverage. Impact: the outbound domain is smaller and button behavior now has a direct module-level test peer while existing `lib/outbound.ts` exports remain compatible.
16
+ - `[Tests]` Added symmetric `tests/outbound-markup.test.ts` coverage for `lib/outbound-markup.ts`, including top-level comment collection, fenced-code exclusion, partial-comment stripping, attribute parsing, and multi-voice planning. Impact: assistant action markup has direct module-level regression coverage instead of relying only on umbrella outbound tests.
17
+ - `[Tests]` Added symmetric `tests/menu-status.test.ts` coverage for `lib/menu-status.ts`, including status reply markup rows, voice-active thinking suppression, callback routing, and status message send/update helpers. Impact: status-menu behavior now has a direct module-level regression peer.
18
+ - `[Tests]` Added symmetric `tests/menu-thinking.test.ts` coverage for `lib/menu-thinking.ts`, including reply markup, thinking-level callbacks, invalid/voice/non-reasoning guards, and menu send/update helpers. Impact: thinking-menu behavior now has direct module-level regression coverage.
19
+ - `[Tests]` Added symmetric `tests/setup.test.ts` coverage for `lib/setup.ts`, including token defaults, prompt mode selection, setup success/failure, and prompt-runtime guard cleanup after errors. Impact: `/telegram-setup` prompt behavior now has direct module-level regression coverage.
20
+ - `[Tests]` Added symmetric `tests/menu-settings.test.ts` coverage for `lib/menu-settings.ts`, including built-in settings markup, detail menu active states, settings mutations, and stale-message fallback toggles. Impact: settings-menu behavior now has direct module-level regression coverage.
21
+ - `[Context]` Cleared the completed backlog down to `No open work` and tightened the `lib/outbound.ts` domain header after button/markup extraction. Impact: historical completion notes stay in the changelog, while backlog and domain ownership now reflect current reality.
22
+ - `[Refactor]` Split native Telegram voice delivery from `lib/outbound.ts` into `lib/outbound-voice.ts`, with symmetric `tests/outbound-voice.test.ts` coverage. Impact: outbound voice upload/provider orchestration now has a direct domain owner and `lib/outbound.ts` is reduced to the outbound surface, command handlers, text transforms, artifact composition, and compatibility re-exports.
23
+ - `[Command Templates]` Replaced `pi-telegram`'s command-template implementation with the current `pi-actors` standard as a deliberate breaking 0.x minor change. Impact: templates now use `parallel` and `when`, support string `timeout`/`delay`/`retry`, inherited default references, `{value??fallback}`, `{flag?yes:no}`, and empty-arg filtering; old local `mode`, `critical`, and `pipe` shapes were removed/migrated to `parallel`, `failure`, and `template: [...]`.
24
+ - `[Docs]` Removed real companion-extension identities from abstract voice-provider examples and README guidance, keeping concrete names only in the explicit Companion Extensions list. Impact: pi-telegram documentation now keeps bridge responsibilities separate from third-party companion-extension ownership.
25
+ - `[Docs]` Integrated public API smoke examples for Extension Sections, raw updates, inbound handlers, outbound handlers, and voice providers into `docs/public-api.md` using only stable public membranes. Impact: companion-extension authors get copyable patterns in the canonical public API guide and avoid removed `@llblab/pi-telegram/lib/*` imports.
26
+ - `[Docs]` Updated README release positioning and corrected Extension Sections docs so `ctx.edit()` owns automatic Back-row insertion, `ctx.open()` is documented as a standalone chat-message sender, and section callbacks appear before built-in menu callbacks. Impact: companion extension authors get an accurate public contract.
27
+ - `[Backlog]` Restored concrete near-term follow-up slices for public API migration, provider lifecycle, smoke examples, diagnostics, voice-domain DAG cleanup, section examples, and voice transcript config clarity. Impact: README's open-work link points to actionable development priorities again.
28
+
29
+ ## 0.12.0: Public API Membranes, Telegram UX Safety, And Extension Interop
30
+
31
+ - `[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.
32
+ - `[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.
33
+ - `[Docs]` Added `docs/ui-style.md` as the focused style guide for inline buttons, toggles, tabs, option lists, cards, and dialogs.
34
+ - `[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.
35
+ - `[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.
36
+ - `[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.
37
+ - `[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.
38
+ - `[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.
39
+ - `[API]` Hardened Telegram Extension Sections by rejecting duplicate section ids and validating `ctx.callbackData()` against Telegram's 64-byte callback-data limit.
40
+ - `[API]` Added `registerTelegramUpdateHandler()` as the matrix-aligned low-level update bus name.
41
+ - `[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.
42
+ - `[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.
43
+ - `[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.
4
44
 
5
45
  ## 0.11.2: Queue Continuation, Compaction Safety, And Settings Polish
6
46
 
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Telegram runtime adapter for π.**
6
6
 
7
- `pi-telegram` turns a private Telegram DM into a session-local operator console for π. It admits work, preserves context, streams readable replies, keeps busy sessions usable through queues, lets other extensions share one bot, and turns assistant-authored intent into native Telegram artifacts. In `0.11.0`, it also becomes a voice-provider platform: companion extensions can supply Telegram transcription and synthesis providers while `pi-telegram` keeps ownership of transport, queueing, and reply policy.
7
+ `pi-telegram` turns a private Telegram DM into a session-local operator console for π. It admits work, preserves context, streams readable replies, keeps busy sessions usable through queues, lets other extensions share one bot, and turns assistant-authored intent into native Telegram artifacts. It is also a voice-provider platform: companion extensions can supply Telegram transcription and synthesis providers while `pi-telegram` keeps ownership of transport, queueing, and reply policy.
8
8
 
9
9
  This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`cb34008`](https://github.com/badlogic/pi-telegram/commit/cb34008460b6c1ca036d92322f69d87f626be0fc) and has since diverged substantially.
10
10
 
@@ -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.
@@ -199,47 +199,19 @@ If `telegram.json` explicitly sets a valid `voice.replyMode`, prompts include co
199
199
 
200
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.
201
201
 
202
- Voice synthesis provider extensions (e.g. `pi-xai-voice`) register a TTS backend at runtime:
203
-
204
- ```typescript
205
- import { registerTelegramVoiceSynthesisProvider } from "@llblab/pi-telegram/lib/voice.ts";
206
- import { recordTelegramRuntimeEvent } from "@llblab/pi-telegram/lib/outbound-handlers.ts";
207
-
208
- // Return path only (backward compatible)
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
- );
215
-
216
- // Return path + transcript caption
217
- const dispose2 = registerTelegramVoiceSynthesisProvider(
218
- async (text, { lang, rate }) => {
219
- const rewritten = rewriteWithSpeechTags(text); // internal TTS optimization
220
- const path = await myTTS(rewritten, { language: lang });
221
- return { audioPath: path, transcriptText: text };
222
- },
223
- );
224
-
225
- // Surface diagnostics in /telegram-status
226
- recordTelegramRuntimeEvent("xai-voice", new Error("TTS complete"), {
227
- phase: "tts",
228
- durationMs: 1200,
229
- });
230
- ```
202
+ Voice synthesis provider extensions register TTS backends at runtime through public API domain subpaths. Multiple synthesis providers can be registered; stable provider registrations pass a durable abstract id owned by the companion extension, such as `"@scope/voice-provider/tts"`. 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.
231
203
 
232
- 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.
204
+ Provider code examples, transcript-caption behavior, and diagnostics patterns live in [Voice Integration](./docs/voice.md) and [Public API](./docs/public-api.md). The important boundary is: providers own TTS and any private optimization, while pi-telegram owns reply policy, prompt context, fallback ordering, and Telegram transport.
233
205
 
234
206
  ### Extension interop
235
207
 
236
- 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.
208
+ 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.
237
209
 
238
210
  ### Extension Sections
239
211
 
240
- 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.
212
+ 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.
241
213
 
242
- 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.
214
+ 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.
243
215
 
244
216
  ### Proactive push
245
217
 
@@ -267,14 +239,16 @@ Modes are `hidden`, `always`, and `interval`. `hidden` means no time line is add
267
239
  - [Changelog](./CHANGELOG.md): completed delivery history.
268
240
  - [Documentation Index](./docs/README.md): technical docs hub.
269
241
  - [Architecture](./docs/architecture.md): runtime and subsystem overview.
270
- - [Inbound Handlers](./docs/inbound-handlers.md): Telegram π preprocessing.
271
- - [Outbound Handlers](./docs/outbound-handlers.md): final text, voice, and artifact pipelines.
272
- - [Voice Integration](./docs/voice.md): voice reply policies, transparent interception, and provider extension API.
242
+ - [Public API](./docs/public-api.md): stable commands, config, package entrypoints, assistant markup, and extension APIs.
243
+ - [Inbound Handlers](./docs/inbound.md): Telegram π preprocessing.
244
+ - [Outbound Handlers](./docs/outbound.md): final text, voice, and artifact pipelines.
273
245
  - [Command Templates](./docs/command-templates.md): portable command-template contract.
274
246
  - [Callback Namespaces](./docs/callback-namespaces.md): callback interop for layered extensions.
275
- - [External Handlers](./docs/external-handlers.md): shared update interception.
276
- - [Extension Sections](./docs/extension-sections.md): Telegram extension sections platform for loading extensions that register UI surfaces.
247
+ - [Updates](./docs/updates.md): shared update interception.
248
+ - [Extension Sections](./docs/sections.md): Telegram extension sections platform for loading extensions that register UI surfaces.
249
+ - [Voice Integration](./docs/voice.md): voice reply policies, transparent interception, and provider extension API.
277
250
  - [Locks](./docs/locks.md): singleton polling ownership.
251
+ - [UI Style](./docs/ui-style.md): inline button, toggle, tab, option-list, card, and dialog style guide.
278
252
 
279
253
  ## Notes
280
254
 
@@ -292,7 +266,7 @@ Third-party extensions that integrate with `pi-telegram`:
292
266
  pi install npm:pi-telegram-tool-status
293
267
  ```
294
268
 
295
- - [`pi-xai-voice`](https://github.com/luxus/pi-xai-voice) — xAI voice companion for Telegram voice replies through the `pi-telegram` voice provider API.
269
+ - [`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.
296
270
 
297
271
  ```bash
298
272
  pi install npm:pi-xai-voice
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, smoke examples, 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