@llblab/pi-telegram 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -2
- package/CHANGELOG.md +22 -2
- package/README.md +38 -10
- package/docs/architecture.md +137 -30
- package/docs/command-templates.md +81 -24
- package/index.ts +33 -42
- package/lib/command-templates.ts +163 -32
- package/lib/config.ts +64 -0
- package/lib/lifecycle.ts +86 -0
- package/lib/menu-settings.ts +139 -21
- package/lib/pi.ts +4 -0
- package/lib/prompts.ts +1 -0
- package/lib/routing.ts +3 -10
- package/lib/time-injection.ts +78 -0
- package/lib/turns.ts +13 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -73,7 +73,8 @@
|
|
|
73
73
|
- 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
74
|
- `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
75
|
- 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
|
-
-
|
|
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 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
|
+
- 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
|
|
77
78
|
- 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
|
|
78
79
|
- `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
|
|
79
80
|
- `telegram_button` has three canonical forms: `<!-- telegram_button: OK -->` for label-only buttons, `<!-- telegram_button label=Continue prompt="Continue with the current plan." -->` for one-line prompts, or `<!-- telegram_button label="Show risks"\nList the main risks first.\n-->` for multiline prompts
|
|
@@ -128,7 +129,8 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
|
|
|
128
129
|
- When durable runtime constraints or repeat bug patterns emerge, record them here instead of burying them in changelog prose
|
|
129
130
|
- When fork identity changes, keep `README.md`, package metadata, and docs aligned so the published package does not point back at stale upstream coordinates
|
|
130
131
|
- 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.
|
|
131
|
-
- Document configuration knobs without UI in the root README when they affect bootstrap, networking, or
|
|
132
|
+
- 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`.
|
|
133
|
+
- 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.
|
|
132
134
|
- 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
|
|
133
135
|
|
|
134
136
|
## 8. Integration Protocols
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.11.2: Queue Continuation, Compaction Safety, And Settings Polish
|
|
6
|
+
|
|
7
|
+
- `[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.
|
|
8
|
+
- `[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.
|
|
9
|
+
- `[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.
|
|
10
|
+
- `[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.
|
|
11
|
+
- `[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.
|
|
12
|
+
- `[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.
|
|
13
|
+
- `[Docs]` Updated the command-template standard with typed args, array placeholders, failure propagation, recover cleanup, and trust-boundary guidance.
|
|
14
|
+
- `[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.
|
|
15
|
+
- `[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.
|
|
16
|
+
- `[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.
|
|
17
|
+
|
|
18
|
+
## 0.11.1: Time Context And Settings Polish
|
|
19
|
+
|
|
20
|
+
- `[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.
|
|
21
|
+
- `[Settings UI]` The proactive push row now uses `📌 Proactive push: on|off`, and proactive push, time, and voice reply submenus use matching emoji headings.
|
|
22
|
+
|
|
3
23
|
## 0.11.0: Voice Provider Platform
|
|
4
24
|
|
|
5
25
|
- `[Voice Synthesis Provider API]` Added a first-class voice synthesis provider surface for Telegram voice replies. Providers register with `registerTelegramVoiceSynthesisProvider()` from `@llblab/pi-telegram/lib/voice.ts`, synthesize text into `.ogg`/`.opus`, may return `{ audioPath, transcriptText }`, and can contribute voice-specific prompt guidance through `getVoicePromptContribution(view)`.
|
|
@@ -131,7 +151,7 @@
|
|
|
131
151
|
## 0.9.4: Temp Dir And Command Template Hotfix
|
|
132
152
|
|
|
133
153
|
- `[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.
|
|
134
|
-
- `[Command Templates]`
|
|
154
|
+
- `[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.
|
|
135
155
|
- `[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.
|
|
136
156
|
- `[Package]` Bumped package metadata to `0.9.4` and kept the lockfile in sync.
|
|
137
157
|
|
|
@@ -183,7 +203,7 @@
|
|
|
183
203
|
- `[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.
|
|
184
204
|
- `[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.
|
|
185
205
|
- `[Package]` Bumped package metadata to `0.8.1` and kept the lockfile in sync.
|
|
186
|
-
- `[Command Template Docs]`
|
|
206
|
+
- `[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.
|
|
187
207
|
- `[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.
|
|
188
208
|
|
|
189
209
|
## 0.8.0: Handler Bus
|
package/README.md
CHANGED
|
@@ -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
|
|
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
|
|
|
@@ -203,17 +206,21 @@ import { registerTelegramVoiceSynthesisProvider } from "@llblab/pi-telegram/lib/
|
|
|
203
206
|
import { recordTelegramRuntimeEvent } from "@llblab/pi-telegram/lib/outbound-handlers.ts";
|
|
204
207
|
|
|
205
208
|
// Return path only (backward compatible)
|
|
206
|
-
const dispose = registerTelegramVoiceSynthesisProvider(
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
);
|
|
210
215
|
|
|
211
216
|
// Return path + transcript caption
|
|
212
|
-
const dispose2 = registerTelegramVoiceSynthesisProvider(
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
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
|
+
);
|
|
217
224
|
|
|
218
225
|
// Surface diagnostics in /telegram-status
|
|
219
226
|
recordTelegramRuntimeEvent("xai-voice", new Error("TTS complete"), {
|
|
@@ -238,6 +245,21 @@ Import `registerTelegramSection()` from `@llblab/pi-telegram/lib/extension-secti
|
|
|
238
245
|
|
|
239
246
|
`telegram.json` can set `proactivePush: true` to send successful local non-Telegram final replies to the paired Telegram chat when no Telegram turn is active. Local prompt text is not mirrored because the bot does not own terminal user messages. The mode is off by default and can be toggled from settings.
|
|
240
247
|
|
|
248
|
+
### Time context
|
|
249
|
+
|
|
250
|
+
`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`.
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{
|
|
254
|
+
"time": {
|
|
255
|
+
"injectionMode": "interval",
|
|
256
|
+
"interval": 3600000
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
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.
|
|
262
|
+
|
|
241
263
|
## Docs
|
|
242
264
|
|
|
243
265
|
- [Project Context](./AGENTS.md): durable engineering conventions and architecture constraints.
|
|
@@ -270,6 +292,12 @@ Third-party extensions that integrate with `pi-telegram`:
|
|
|
270
292
|
pi install npm:pi-telegram-tool-status
|
|
271
293
|
```
|
|
272
294
|
|
|
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.
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
pi install npm:pi-xai-voice
|
|
299
|
+
```
|
|
300
|
+
|
|
273
301
|
## License
|
|
274
302
|
|
|
275
303
|
MIT
|
package/docs/architecture.md
CHANGED
|
@@ -28,11 +28,11 @@ Current runtime areas use these ownership boundaries:
|
|
|
28
28
|
- `config` / `setup`: persisted bot/session pairing state, authorization, first-user pairing, token prompting, env fallback, validation, and config persistence.
|
|
29
29
|
- `locks` / `polling`: singleton `locks.json` ownership, takeover/restart semantics, long-poll controller state, update offset persistence, and poll-loop runtime wiring.
|
|
30
30
|
- `updates` / `routing`: update classification/execution planning, paired authorization, reactions, edits, callbacks, and inbound route composition.
|
|
31
|
-
- `media` / `text-groups` / `turns` / `inbound-handlers`: text/media extraction, media-group debounce, long-text split coalescing, inbound downloads, configured and programmatic inbound text/media handler execution, turn building/editing, image reads, and legacy `attachmentHandlers` compatibility.
|
|
31
|
+
- `media` / `text-groups` / `time-injection` / `turns` / `inbound-handlers`: text/media extraction, media-group debounce, long-text split coalescing, optional per-chat wall-clock prompt context, inbound downloads, configured and programmatic inbound text/media handler execution, turn building/editing, image reads, and legacy `attachmentHandlers` compatibility.
|
|
32
32
|
- `queue`: queue item contracts, lane admission/order, stores, mutations, dispatch readiness/runtime, prompt/control enqueueing, and session/agent/tool lifecycle sequencing.
|
|
33
33
|
- `runtime`: session-local coordination primitives: counters, lifecycle flags, setup guard, abort handler, typing-loop timers, prompt-dispatch flags, and agent-end reset binding.
|
|
34
34
|
- `model` / `menu-model` / `menu-thinking` / `menu-status` / `menu` / `menu-queue` / `menu-settings` / `commands`: model identity/thinking levels, scoped model resolution, in-flight switching, model/thinking/status/queue/settings menu UI, inline application callback composition, slash commands, and bot command registration.
|
|
35
|
-
-
|
|
35
|
+
- `extension-sections`: structured external Telegram menu sections registered by ordinary pi extensions; owns section registry, compact section callback tokens, section render/callback dispatch, safe section runtime ports, and diagnostics.
|
|
36
36
|
- `keyboard`: shared Telegram inline-keyboard reply-markup structure; feature domains own callback semantics and button construction.
|
|
37
37
|
- `preview` / `replies` / `rendering`: preview lifecycle/transports, final reply delivery and reply parameters, Telegram HTML Markdown rendering, chunking, and stable-preview snapshots.
|
|
38
38
|
- `outbound-handlers`: outbound text transformation, assistant-authored outbound comments, generated reply artifacts, inline-keyboard callbacks, and post-`agent_end` outbound action delivery.
|
|
@@ -44,7 +44,7 @@ Current runtime areas use these ownership boundaries:
|
|
|
44
44
|
Boundary invariants:
|
|
45
45
|
|
|
46
46
|
- Constants and state types live with their owning domains; do not reintroduce shared buckets such as `lib/constants.ts` or `lib/types.ts`
|
|
47
|
-
- Shared Telegram inline-keyboard structure belongs to `keyboard`; application-control labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue`;
|
|
47
|
+
- Shared Telegram inline-keyboard structure belongs to `keyboard`; application-control labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue`; external section labels, callbacks, and dispatch stay in `extension-sections`; core queue mechanics stay in `queue`
|
|
48
48
|
- Domain helpers use narrow structural projections when that avoids importing concrete wire DTOs or broader runtime objects unnecessarily
|
|
49
49
|
- Preview appearance stays in `rendering`; preview transport/lifecycle stays in `preview`
|
|
50
50
|
- Direct `node:*` file-operation imports stay in owning domains, not in `index.ts`
|
|
@@ -64,7 +64,19 @@ Because `ctx.ui.input()` only exposes placeholder text, the bridge uses `ctx.ui.
|
|
|
64
64
|
|
|
65
65
|
## Runtime Ownership
|
|
66
66
|
|
|
67
|
-
Telegram bot configuration stays in `~/.pi/agent/telegram.json
|
|
67
|
+
Telegram bot configuration stays in `~/.pi/agent/telegram.json`. Singleton runtime ownership lives separately in `~/.pi/agent/locks.json` under `@llblab/pi-telegram`.
|
|
68
|
+
|
|
69
|
+
Ownership lifecycle:
|
|
70
|
+
|
|
71
|
+
- `/telegram-connect` acquires or moves the singleton lock before polling starts.
|
|
72
|
+
- `/telegram-disconnect` stops polling and releases the lock.
|
|
73
|
+
- Session start resumes polling when the existing lock already points at the current `pid`/`cwd`.
|
|
74
|
+
- After a full π process restart, session start may replace a stale lock from the same `cwd` and resume polling automatically.
|
|
75
|
+
- Session start does not create new ownership from an inactive lock, a live external lock, or a stale lock from another directory.
|
|
76
|
+
- Session replacement suspends polling and ownership watchers without releasing the lock, allowing the next session-start hook in the same `pid`/`cwd` to resume from explicit ownership.
|
|
77
|
+
- When a live external owner exists, `/telegram-connect` asks whether to move singleton ownership to the current π instance.
|
|
78
|
+
|
|
79
|
+
Active owners poll the lock through a snapshotted ownership context. Long-lived timers therefore avoid stale π contexts after `/new`; they stop local polling when `locks.json` no longer points at their own `pid`/`cwd`, without deleting the new owner lock. Deleting `locks.json` resets runtime ownership without deleting Telegram configuration.
|
|
68
80
|
|
|
69
81
|
## Message And Queue Flow
|
|
70
82
|
|
|
@@ -79,9 +91,10 @@ Telegram bot configuration stays in `~/.pi/agent/telegram.json`; singleton runti
|
|
|
79
91
|
7. Configured inbound handlers may run on raw text or downloaded files by MIME wildcard, Telegram attachment type, or generic match selector; command templates receive safe command-arg substitution for `{text}`, `{file}`, `{mime}`, and `{type}` where applicable
|
|
80
92
|
8. Matching media/file handlers are tried in config order: a non-zero exit records diagnostics and falls back to the next matching handler, while the first successful handler stops the chain
|
|
81
93
|
9. Local attachments stay visible under `[attachments] <directory>` with relative file entries, and handler stdout is appended under `[outputs]` before the agent sees the turn; failed handlers omit output while keeping the attachment entry
|
|
82
|
-
10.
|
|
83
|
-
11.
|
|
84
|
-
12.
|
|
94
|
+
10. Optional `time` config may add a compact final `[time]` prompt line after attachment/output/voice sections, either every turn or per-chat after the configured millisecond interval, using the system timezone
|
|
95
|
+
11. A `PendingTelegramTurn` is created and queued locally
|
|
96
|
+
12. Telegram `edited_message` updates are routed separately and update a matching queued turn when the original message has not been dispatched yet
|
|
97
|
+
13. The queue dispatcher sends the turn into π only when dispatch is safe
|
|
85
98
|
|
|
86
99
|
### Queue Safety Model
|
|
87
100
|
|
|
@@ -100,7 +113,19 @@ Admission contract:
|
|
|
100
113
|
- Priority prompt queue: a waiting prompt promoted by `👍`, `⚡️`, `❤️`, `🕊`, or `🔥` uses `kind: prompt`, `queueLane: priority`, and dispatches at rank `1`.
|
|
101
114
|
- Default prompt queue: normal Telegram text/media turns use `kind: prompt`, `queueLane: default`, and dispatch at rank `2`.
|
|
102
115
|
|
|
103
|
-
The command action itself carries its execution mode
|
|
116
|
+
The command action itself carries its execution mode. The queue domain exposes lane contracts for admission mode, dispatch rank, and allowed item kinds.
|
|
117
|
+
|
|
118
|
+
Queue validation rules:
|
|
119
|
+
|
|
120
|
+
- Queue append and planning paths validate lane admission.
|
|
121
|
+
- Malformed control/default or other invalid lane pairings fail predictably instead of silently changing priority.
|
|
122
|
+
- Synthetic control actions and Telegram prompts share one stable ordering model while still rendering distinctly in status output.
|
|
123
|
+
|
|
124
|
+
Status rendering rules:
|
|
125
|
+
|
|
126
|
+
- Busy labels distinguish `active`, `dispatching`, `queued`, `tool running`, `model`, and `compacting`.
|
|
127
|
+
- Priority prompts and priority control items are marked with `⚡`.
|
|
128
|
+
- If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, the status remains yellow `active` instead of degrading to green `connected`.
|
|
104
129
|
|
|
105
130
|
A dispatched prompt remains in the queue until `agent_start` consumes it. That keeps the active Telegram turn bound correctly for previews, attachments, abort handling, and final reply delivery.
|
|
106
131
|
|
|
@@ -112,9 +137,49 @@ Dispatch is gated by:
|
|
|
112
137
|
- `ctx.isIdle()` being true
|
|
113
138
|
- `ctx.hasPendingMessages()` being false
|
|
114
139
|
|
|
115
|
-
|
|
140
|
+
These gates prevent queue races around rapid follow-ups, `/compact`, and mixed local plus Telegram activity.
|
|
141
|
+
|
|
142
|
+
Post-agent-end dispatch retries use a session-bound deferred dispatcher:
|
|
143
|
+
|
|
144
|
+
- It activates on session start.
|
|
145
|
+
- It cancels timers on session shutdown.
|
|
146
|
+
- It skips callbacks from older generations before they touch `ExtensionContext`.
|
|
147
|
+
|
|
148
|
+
Telegram `/start` and hidden compatibility shortcuts `/status`, `/model`, `/thinking`, `/queue`, and `/settings` execute immediately. The dispatch controller still serializes deferred control items so a queued control action must settle before the next queued action can dispatch.
|
|
116
149
|
|
|
117
|
-
|
|
150
|
+
### Application Menu Shape
|
|
151
|
+
|
|
152
|
+
`/start` opens the main application menu. It contains visible command help, compact command-only prompt-template rows when π exposes Telegram-compatible prompt-template names, status rows (`Status`, `Usage`, `Cost`, `Context`), and top-level buttons for model, thinking, and queue sections.
|
|
153
|
+
|
|
154
|
+
Menu rules:
|
|
155
|
+
|
|
156
|
+
- The `Status` row reports `compacting` while a Telegram `/compact` run is active, and the bridge sends Telegram's native `typing` chat action as a keepalive for the same compaction window.
|
|
157
|
+
- The Queue button includes the current queued-item count.
|
|
158
|
+
- Hidden compatibility shortcuts `/help`, `/status`, `/model`, `/thinking`, and `/queue` jump directly to their corresponding menu screens.
|
|
159
|
+
- `/settings` opens the hidden settings menu for bridge toggles such as proactive push, voice reply mode, and `time.injectionMode`.
|
|
160
|
+
- Settings options open detail submenus. Boolean settings use Back plus green/black/yellow `on` and `off` controls; list-like settings such as time injection use explicit mode names like `hidden`, `always`, and `interval`.
|
|
161
|
+
- Command emoji come from the `commands` domain map so visible command descriptions and matching menu buttons share one fixed adornment source.
|
|
162
|
+
- Prompt-template commands use a fixed `🧩` marker, map π template names to Telegram-safe aliases such as `fix-tests` → `/fix_tests`, stay visible only inside the `/start` menu, and expand before queueing because `ExtensionAPI.sendUserMessage()` bypasses π prompt-template expansion for extension-originated messages.
|
|
163
|
+
|
|
164
|
+
Navigation and ownership:
|
|
165
|
+
|
|
166
|
+
- Every submenu starts with a top Back row so navigation stays anchored near the original user message above the inline keyboard.
|
|
167
|
+
- Model-menu pagination controls sit near the top; tapping the pagination indicator opens a compact page picker headed by `<b>Choose a page:</b>`.
|
|
168
|
+
- Tapping a model opens a detail submenu with Back, ☑️ Activate/🟢 Active selection, and yellow/black-marked Scoped/All membership tabs.
|
|
169
|
+
- `model` owns core model identity/switching semantics.
|
|
170
|
+
- `menu-model` owns model-menu state, scoped model pages, model detail rendering, scoped-list persistence planning, and model-menu rendering.
|
|
171
|
+
- `menu-thinking` owns thinking-menu text, reply markup, callback handling, and message rendering.
|
|
172
|
+
- `menu-status` owns status-menu payloads, status callback handling, and status-message rendering.
|
|
173
|
+
- `menu-queue` owns queue-menu UI only.
|
|
174
|
+
|
|
175
|
+
Queue menu rendering:
|
|
176
|
+
|
|
177
|
+
- Queue items render under a compact `<b>Queue:</b>` heading, top-to-bottom in dispatch order.
|
|
178
|
+
- Items are numbered and marked with `⚡` for priority prompts or `📎` for prompts with attachments.
|
|
179
|
+
- An empty queue renders bold message text with the bottom-filled `⌛` hourglass plus the top Main menu button.
|
|
180
|
+
- Non-empty queue states keep the running `⏳` hourglass.
|
|
181
|
+
- Selecting an item opens a submenu with the queue item number, full queued prompt text, Back, side-by-side Priority/Normal tabs, and Cancel.
|
|
182
|
+
- If a callback targets an item that has already left the queue, the menu refreshes the list instead of applying a stale mutation.
|
|
118
183
|
|
|
119
184
|
### Abort Behavior
|
|
120
185
|
|
|
@@ -153,32 +218,74 @@ Preferred order:
|
|
|
153
218
|
|
|
154
219
|
Draft streaming can remain as a plain-text fallback path, but rich Telegram previews are driven through editable messages and stable-block snapshot selection.
|
|
155
220
|
|
|
156
|
-
|
|
221
|
+
### Response Context
|
|
222
|
+
|
|
223
|
+
Telegram prompt responses use explicit delivery context to attach outbound text, rich previews, errors, attachment notices, and uploads as Telegram replies to the source prompt when possible.
|
|
224
|
+
|
|
225
|
+
Reply metadata rules:
|
|
226
|
+
|
|
227
|
+
- Reply metadata is opt-in per delivery path.
|
|
228
|
+
- It uses `reply_parameters` with `allow_sending_without_reply: true`.
|
|
229
|
+
- It is applied only to the first chunk of split long responses; continuation chunks are sent as normal adjacent messages.
|
|
230
|
+
- Media-group turns reply to the turn's representative `replyToMessageId`, not to every source message in the group.
|
|
157
231
|
|
|
158
|
-
|
|
232
|
+
Long text split coalescing is intentionally conservative. Only human text messages at or above the 3600-character near-limit threshold open the short debounce window. Immediate same-chat/user contiguous text tails join that prompt; commands, bot messages, captions, media groups, and normal short follow-ups bypass the coalescer.
|
|
159
233
|
|
|
160
|
-
|
|
234
|
+
### Outbound Files
|
|
161
235
|
|
|
162
|
-
|
|
236
|
+
Outbound files are sent only after the active Telegram turn completes. They must be staged through the `telegram_attach` tool, are staged atomically per tool call, and are checked against a default 50 MiB limit configurable through `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES` or `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES`. Delivery uses file-backed multipart blobs so large sends do not require preloading whole files into memory.
|
|
237
|
+
|
|
238
|
+
### Assistant-Authored Actions
|
|
239
|
+
|
|
240
|
+
Assistant-authored outbound actions use final-message markup instead of agent tool calls. Preview updates strip closed top-level HTML comments and currently open/partial top-level comment starts before rendering, so users do not see transient metadata even when streaming flushes happen after only `<`, `<!`, or `<!--`.
|
|
241
|
+
|
|
242
|
+
On `agent_end`, the bridge removes top-level comments from the Markdown text reply, but treats these column-zero top-level blocks specially before delivery:
|
|
243
|
+
|
|
244
|
+
- `<!-- telegram_voice ... -->`
|
|
245
|
+
- `<!-- telegram_button ... -->`
|
|
246
|
+
|
|
247
|
+
Comments inside fenced code, quotes, lists, or indented examples stay literal, including fenced blocks with Markdown-valid indented closing fences.
|
|
248
|
+
|
|
249
|
+
Voice delivery uses one fallback pipeline:
|
|
250
|
+
|
|
251
|
+
1. Configured `outboundHandlers` with `type: "voice"`
|
|
252
|
+
2. Programmatic `voice` handlers
|
|
253
|
+
3. Registered synthesis providers from `lib/voice.ts`
|
|
254
|
+
|
|
255
|
+
The bridge extracts body text, `text="..."`, or colon shorthand, asks the pipeline for an `.ogg`/`.opus` artifact, validates native voice format, and uploads the generated file via Telegram `sendVoice`. When delivery fails, the queue runtime records diagnostics and falls back to the planned text reply when no text was already delivered. Synthesis providers own TTS, speech rewriting, transcript choice, and format conversion.
|
|
256
|
+
|
|
257
|
+
Button blocks are built in. Each `telegram_button` block becomes one inline-keyboard button on the final text, and callback clicks enqueue the configured prompt text as a normal Telegram prompt turn. The `telegram_button: Label` shorthand uses the same text for label and prompt, `prompt="..."` supports explicit one-line prompts, and body-form buttons use the body as the prompt.
|
|
258
|
+
|
|
259
|
+
Unknown callback data that does not match pi-telegram-owned prefixes (`tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `section:`) is forwarded to π as `[callback] <data>` after built-in handlers decline it. Layered callback payloads should follow the [Callback Namespace Standard](./callback-namespaces.md). Structured menu integrations should use the [Telegram Extension Sections Standard](./extension-sections.md) instead of hand-rolled fallback callbacks.
|
|
260
|
+
|
|
261
|
+
### Proactive Push And Mobile Guidance
|
|
262
|
+
|
|
263
|
+
When proactive push is enabled, successful local non-Telegram final replies are sent to the paired chat. Local prompt text is not sent because the bot does not own or mirror terminal user messages. This keeps terminal-originated results visible in Telegram without changing Telegram-originated turn delivery.
|
|
264
|
+
|
|
265
|
+
Technical Markdown, code, tables, formulas, and numbered lists stay in the text channel when appropriate while TTS-friendly voice messages and tappable continuations do not require `telegram_attach` or extra transport tools. Telegram prompt guidance targets about 37 visible cells for tables, dense list items, and compact text blocks because emoji and other wide glyphs make raw character counts misleading on mobile screens.
|
|
163
266
|
|
|
164
267
|
## Interactive Controls
|
|
165
268
|
|
|
166
|
-
The bridge exposes Telegram-side session controls in addition to regular chat forwarding.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
- `/start
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
- `/
|
|
174
|
-
- `/
|
|
175
|
-
- `/
|
|
176
|
-
- `/
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
|
|
180
|
-
- `/telegram-
|
|
181
|
-
-
|
|
269
|
+
The bridge exposes Telegram-side session controls in addition to regular chat forwarding. Menu layout details live in [Application Menu Shape](#application-menu-shape); this section summarizes the command/control surface.
|
|
270
|
+
|
|
271
|
+
Telegram chat controls:
|
|
272
|
+
|
|
273
|
+
- `/start`: opens the main application menu and runs immediately even while generation is active.
|
|
274
|
+
- `/model`, `/thinking`, `/queue`: hidden shortcuts for opening the matching menu sections directly.
|
|
275
|
+
- `/compact`: triggers π session compaction when the bridge is idle.
|
|
276
|
+
- `/next`: dispatches the next queued turn, aborting the active run first when π is busy.
|
|
277
|
+
- `/continue`: enqueues a Telegram-owned priority `continue` prompt without aborting the current turn.
|
|
278
|
+
- `/abort`: aborts the active Telegram-owned run while preserving queued items for manual continuation.
|
|
279
|
+
- `/stop`: aborts the active Telegram-owned run and clears waiting Telegram queue items.
|
|
280
|
+
|
|
281
|
+
Pi-side diagnostics and settings:
|
|
282
|
+
|
|
283
|
+
- `/telegram-status`: renders grouped diagnostics for connection, polling, execution, queue, and the recent redacted runtime/API event ring.
|
|
284
|
+
- `/telegram-settings`: exposes π-side bridge settings; currently this includes proactive push backed by the same `telegram.json` flag as the hidden Telegram `/settings` menu.
|
|
285
|
+
|
|
286
|
+
Queue reactions are shortcut controls for waiting text, voice, file, image, and media-group turns. Matching uses the turn's source Telegram message ids. `👍`, `⚡️`, `❤️`, `🕊`, and `🔥` promote waiting prompts; `👎`, `👻`, `💔`, `💩`, and `🗑` remove waiting turns because ordinary Telegram DM message deletions are not exposed through the Bot API polling path this bridge uses.
|
|
287
|
+
|
|
288
|
+
The `/telegram-status` event ring records transport/API, polling/update, prompt-dispatch, control-action, typing, compaction, setup, session-lifecycle, and attachment queue/delivery failures. Benign unchanged edit responses and unsupported empty draft-clear attempts are filtered out so expected preview transport noise does not obscure real failures.
|
|
182
289
|
|
|
183
290
|
## In-Flight Model Switching
|
|
184
291
|
|