@llblab/pi-kit 0.1.0 → 0.1.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/BACKLOG.md +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +0 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +28 -2
- package/node_modules/@llblab/pi-telegram/README.md +5 -3
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +10 -0
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +7 -6
- package/node_modules/@llblab/pi-telegram/docs/voice.md +11 -1
- package/node_modules/@llblab/pi-telegram/index.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +18 -2
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +5 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +8 -15
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +24 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +19 -3
- package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +4 -2
- package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +7 -6
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +7 -2
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +14 -2
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +70 -4
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +56 -19
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +2 -2
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +10 -2
- package/package.json +9 -3
package/BACKLOG.md
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.1.2 - 2026-08-20
|
|
6
|
+
|
|
7
|
+
- `Trusted Release Automation`: Adds tag-gated validation, npm Trusted Publisher provenance, immutable package/tag identity checks, public package verification, and workflow-owned GitHub Release creation through `.github/workflows/release.yml`.
|
|
8
|
+
|
|
9
|
+
## 0.1.1 - 2026-08-20
|
|
10
|
+
|
|
11
|
+
- `Telegram Release`: Advances `@llblab/pi-telegram` to `0.36.7`, bringing proactive prompt-button delivery, explicit connection-state context, scoped Telegram control activation, provider-neutral voice configuration guidance, separated tool errors, host compatibility, prompt-block compatibility, and orphan-journal protection into the bundled kit.
|
|
12
|
+
|
|
5
13
|
## 0.1.0 - 2026-08-18
|
|
6
14
|
|
|
7
15
|
- `Package`: Established a version-pinned Pi distribution bundling Actors, Codex usage, Grow Loop, and Telegram extensions with their declared Skills behind one installable package.
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| `@llblab/pi-actors` | `0.49.1` | Extension and Skills |
|
|
10
10
|
| `@llblab/pi-codex-usage` | `0.9.2` | Extension |
|
|
11
11
|
| `@llblab/pi-grow-loop` | `0.7.1` | Extension and Skills |
|
|
12
|
-
| `@llblab/pi-telegram` | `0.36.
|
|
12
|
+
| `@llblab/pi-telegram` | `0.36.7` | Extension and Skills |
|
|
13
13
|
|
|
14
14
|
Versions are exact by design. Updating an extension does not change an installed kit until this repository explicitly advances that dependency and publishes a new kit version.
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ Versions are exact by design. Updating an extension does not change an installed
|
|
|
18
18
|
From npm after publication:
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
pi install npm:@llblab/pi-kit@0.1.
|
|
21
|
+
pi install npm:@llblab/pi-kit@0.1.1
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
For local development:
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
_This file owns unresolved project work only. Completed behavior belongs in `CHANGELOG.md`; durable contracts belong in `AGENTS.md` and `/docs`._
|
|
4
4
|
|
|
5
|
-
- [ ] `Automated release publication live proof`: Exercise the implemented tag-triggered release path on `v0.36.1` and verify reusable full validation, exact tag/version/lock/changelog preflight, npm Trusted Publisher provenance, idempotent package identity, public tarball contents, and one workflow-owned GitHub Release before declaring manual npm publication retired.
|
|
6
5
|
- [ ] `Operator-confirmed pairing`: Replace first-contact automatic ownership with an explicit pending request that the trusted Pi interface can allow or reject before `allowedUserId` is persisted or any Telegram prompt executes; retain manual preconfiguration, bound pending-request lifetime and multiplicity, fail closed across session/transport replacement, and cover unauthorized-first-contact races.
|
|
7
6
|
- [ ] `Environment-backed bot tokens`: Let each profile store an exact Pi-native `$ENV_VAR` or `${ENV_VAR}` reference in `telegram.json` instead of copying the resolved secret; preserve the originating alias when `/telegram-setup` prefills from a supported environment variable, resolve only at validation/activation boundaries, fail closed with a redacted named-variable diagnostic when unresolved, retain literal-token compatibility, and cover named profiles, reload, persistence, status, and secret-redaction paths.
|
|
8
7
|
- [ ] [`Inference bypass Generative Apps`](./docs/generative-apps.md): Let the agent install and bind one managed JavaScript app owner for a generated-prompt prefix so deterministic controls can bypass model inference without creating another button grammar.
|
|
@@ -2,7 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.36.7: Context-Aware Proactive Controls
|
|
6
|
+
|
|
7
|
+
- `Proactive Prompt Buttons`: Plans valid top-level `telegram_button` comments before local/autonomous assistant segments enter Rich or HTML proactive delivery, preserving the ordinary callback store and renderer path instead of silently hiding correct controls.
|
|
8
|
+
- `Connection-Aware Agent Context`: Successful setup/connect and disconnect commands queue a hidden connection-state note for delivery with the next agent turn without triggering one. Connected system guidance and Telegram Skills now distinguish transport capability from user intent, reserving proactive Telegram controls for Telegram-originated turns or explicit requests.
|
|
9
|
+
|
|
10
|
+
## 0.36.6: Orphan Journal Guard
|
|
11
|
+
|
|
12
|
+
- `Orphan Journal Guard`: Rejects a missing journal snapshot when immutable revision segments still exist, preventing later admission from replacing uncertain durable authority with a new revisionless snapshot while preserving snapshot-first compaction and fail-closed recovery.
|
|
13
|
+
- `Voice Configuration Guidance`: Guides agents from voice-configuration requests to provider-neutral `telegram.json` command templates, ordered STT fallbacks, OGG/Opus validation, and companion-provider alternatives while preserving `hidden` as the useful default for explicit `telegram_voice` delivery.
|
|
14
|
+
|
|
15
|
+
## 0.36.5: Separated Tool Errors
|
|
16
|
+
|
|
17
|
+
- `Separated Tool Errors`: Prefixes Telegram attachment and direct-message Tool failures with one normalized leading newline so Pi renders error evidence on a visually separated line beneath the Tool name without changing the underlying diagnostic text.
|
|
18
|
+
|
|
19
|
+
## 0.36.4: Async Host Settings Compatibility
|
|
20
|
+
|
|
21
|
+
- `Async Host Settings`: Normalizes synchronous or asynchronous settings construction plus legacy enabled-model methods or generic `get` / `set` services at the Pi adapter boundary before model-menu reads and scoped-model persistence, improving compatible-host interoperability without changing native Pi behavior or weakening required `agent_settled` semantics.
|
|
22
|
+
|
|
23
|
+
## 0.36.3: Ordered Prompt Compatibility
|
|
24
|
+
|
|
25
|
+
- `Prompt Compatibility`: Preserves plain and ordered-block system prompts across Pi-compatible runtimes; Telegram guidance remains a distinct block when the host supplies blocks, while unavailable transport strips only Telegram tool metadata without collapsing unrelated prompt context.
|
|
26
|
+
|
|
27
|
+
## 0.36.2: Provider-Compatible Controls
|
|
28
|
+
|
|
29
|
+
- `Provider-Compatible Bind Schema`: Exposes `telegram_bind` through one top-level JSON object schema while retaining runtime enforcement of mutually exclusive install and invocation forms, avoiding providers that reject top-level union schemas before any tool call can run.
|
|
30
|
+
- `Reaction Shortcut Naming`: Removes obsolete reaction-action labels from setup guidance now that queue reaction semantics use their current naming.
|
|
31
|
+
- `Compact Thinking Picker`: Keeps `off` on a distinct full-width row and groups the six ordered reasoning intensities into two three-button rows, reducing menu height without changing thinking-level values or callbacks.
|
|
6
32
|
|
|
7
33
|
## 0.36.1: Adaptive Control Surfaces
|
|
8
34
|
|
|
@@ -12,7 +38,7 @@
|
|
|
12
38
|
- `Destructive Skip State`: Changes the active queue Skip selector from yellow to red and records the indicator in the UI registry, making deferred prompt removal visually distinct from Priority, Scoped, and other elevated but non-destructive states.
|
|
13
39
|
- `Worker Cancellation Fence`: Makes Generative App workers acknowledge child-process abort handling before parent termination, with a bounded forced fallback for synchronously blocked methods, preventing Linux CI load from orphaning a cancelled process long enough to commit its delayed side effect.
|
|
14
40
|
- `Capability App Routing`: Telegram interaction now prefers an already-advertised capability-owned Generative App for repeated controls before synthesizing one-shot prompt buttons, while the bridge remains generic and neither discovers capability-specific apps nor owns their state or identities.
|
|
15
|
-
- `Hybrid Surface
|
|
41
|
+
- `Hybrid Surface Routing`: Generated Control Surface and Generative Apps now cross-route over one logical button matrix and shared JSON/CML runtime notation: ephemeral agent composition stays separate from reusable scripts, while one app view may combine deterministic bound methods with ordinary model-mediated prompts and no third button Skill is introduced.
|
|
16
42
|
|
|
17
43
|
## 0.36.0: Generative Apps
|
|
18
44
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
It is a **runtime adapter**, not a remote terminal. Start or supervise work in the Pi TUI, then continue from Telegram while away from the keyboard. Each Telegram destination follows a running Pi instance and sends prompts into that instance's currently active session; it is not permanently bound to one session file or session identity. The bridge preserves Pi session semantics instead of pretending Telegram is a PTY, shell, process launcher, or session browser. That boundary is the product: Telegram gets safe runtime handles, not raw terminal power.
|
|
10
10
|
|
|
11
|
-
Every completed intermediate commentary block from a Telegram-originated turn is delivered once as its own message before the existing final reply. Proactive push is enabled by default for local, autonomous, and unclassified extension follow-up work: `assistant.proactivePush` projects visible checkpoints and the final answer to the authorized Telegram target once and in order; set it explicitly to `false` to disable only that local/autonomous projection. Neither path mirrors local prompts, thinking, tool traffic, token deltas, or stale-generation work. The separate `Activity` setting defaults to `verbose` so new installations discover collapsed provider-exposed thinking and tool evidence immediately; operators can narrow it to one class or choose `quiet`. See [Outbound](docs/outbound.md#public-assistant-output) and the [configuration reference](docs/public-api.md#configuration-api).
|
|
11
|
+
Every completed intermediate commentary block from a Telegram-originated turn is delivered once as its own message before the existing final reply. Proactive push is enabled by default for local, autonomous, and unclassified extension follow-up work: `assistant.proactivePush` projects visible checkpoints and the final answer to the authorized Telegram target once and in order, preserving assistant-authored `telegram_button` comments as interactive prompt buttons; set it explicitly to `false` to disable only that local/autonomous projection. Neither path mirrors local prompts, thinking, tool traffic, token deltas, or stale-generation work. The separate `Activity` setting defaults to `verbose` so new installations discover collapsed provider-exposed thinking and tool evidence immediately; operators can narrow it to one class or choose `quiet`. See [Outbound](docs/outbound.md#public-assistant-output) and the [configuration reference](docs/public-api.md#configuration-api).
|
|
12
12
|
|
|
13
13
|
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.
|
|
14
14
|
|
|
@@ -28,6 +28,8 @@ pi install git:github.com/llblab/pi-telegram
|
|
|
28
28
|
|
|
29
29
|
The 0.21 extension platform requires Pi `0.80.6` or newer. Its Activity API uses the public `agent_settled` lifecycle event to keep retries/continuations under one activity identity and release that identity only after the run fully settles.
|
|
30
30
|
|
|
31
|
+
Pi is the primary and only officially supported host. Narrow host-neutral adapters preserve ordered prompt blocks and normalize synchronous or asynchronous legacy/generic settings services for Pi-compatible hosts, but this is best-effort compatibility rather than an OMP support guarantee. Alternate-host shims must still reproduce required Pi lifecycle semantics—especially `agent_settled`—and their maintainers own ongoing validation.
|
|
32
|
+
|
|
31
33
|
## Quick Start
|
|
32
34
|
|
|
33
35
|
### 1. Create a Telegram bot
|
|
@@ -86,7 +88,7 @@ Enable the optional capabilities the bridge needs in the [@BotFather](https://t.
|
|
|
86
88
|
|
|
87
89
|
1. Enable guest mode so the bot can answer mentions and replies in chats where it is not a member.
|
|
88
90
|
2. Enable private-chat Threaded Mode; when it is available, one live instance becomes the profile's leader and later visible Pi instances register as followers. Without it, the bridge stays in classic single-owner DM mode.
|
|
89
|
-
3. Make the bot an administrator in any chat where the queue reaction shortcuts
|
|
91
|
+
3. Make the bot an administrator in any chat where the queue reaction shortcuts should work. Reaction updates require admin rights, so the shortcuts silently do nothing in non-admin chats; private chats deliver reactions without admin rights.
|
|
90
92
|
|
|
91
93
|
## What It Feels Like
|
|
92
94
|
|
|
@@ -216,7 +218,7 @@ Inbound files land under `<agent-dir>/tmp/telegram` and default to a 50 MiB limi
|
|
|
216
218
|
|
|
217
219
|
### Voice And Media
|
|
218
220
|
|
|
219
|
-
Voice notes, audio, images, PDFs, and other media can pass through configured inbound handlers, programmatic handlers, or registered STT providers. Outbound voice can use configured `outboundHandlers` or registered TTS providers; `pi-telegram` owns reply policy and Telegram transport, while providers own synthesis.
|
|
221
|
+
Voice notes, audio, images, PDFs, and other media can pass through configured inbound handlers, programmatic handlers, or registered STT providers. Outbound voice can use configured `outboundHandlers` or registered TTS providers; `pi-telegram` owns reply policy and Telegram transport, while providers own synthesis. Configure provider-neutral local/API pipelines and ordered fallbacks through [`telegram.json` command templates](./docs/voice.md#choose-an-integration-path). The default `hidden` reply mode still supports intentional voice delivery through explicit `telegram_voice` actions; `mirror` and `always` add automatic voice policy. Explicit actions accept either a JSON object or compact double-quoted attributes, with equivalent `text` and `value` payload keys and one colon-free action marker.
|
|
220
222
|
|
|
221
223
|
### Buttons And Callbacks
|
|
222
224
|
|
|
@@ -88,6 +88,16 @@ The repository uses a **Flat Domain DAG**:
|
|
|
88
88
|
- `bindings` / `lifecycle` / `prompts` / `prompt-templates` / `pi`: Pi-facing command/tool/hook registration and cohesive cross-domain binding assembly, including queue mutation/dispatch/watchdog composition over admission and transport ports; session-generation fencing and start/shutdown sequencing across Queue, grouped input, Delivery, polling, capability monitor, follower refresh, and assistant-output projection; Telegram prompt guidance; prompt-template discovery/expansion; and centralized direct Pi SDK imports.
|
|
89
89
|
- `command-templates`: shell-free command-template helpers, composition expansion, placeholder substitution, executable resolution, warnings, and retry/timeout semantics.
|
|
90
90
|
|
|
91
|
+
### Host Compatibility Boundary
|
|
92
|
+
|
|
93
|
+
Pi is the primary and only officially supported host. `pi-telegram` may still accept narrow, host-neutral representation differences at its existing Pi-facing boundary when they preserve native Pi behavior and do not create a second runtime policy layer:
|
|
94
|
+
|
|
95
|
+
- `prompts` preserves either Pi's plain system-prompt string or an ordered block array supplied by a compatible host, appending Telegram guidance without collapsing host-owned blocks.
|
|
96
|
+
- `pi` normalizes settings-manager construction that is either synchronous or asynchronous, then adapts either Pi's legacy enabled-model methods or a generic `get` / `set` settings service before model-menu reads and scoped-model persistence use it. Hosts without an explicit reload method rely on fresh asynchronous construction; durable writes still require `flush`.
|
|
97
|
+
- `lifecycle` continues to require Pi's semantic `agent_settled` boundary. It does not infer terminal settlement from host-specific `agent_end`, retry, or stop events; a compatibility shim must reproduce that contract before it can safely support activity identity and unrecovered-error finalization.
|
|
98
|
+
|
|
99
|
+
This boundary uses no host-name detection, host package dependency, prototype patching, hidden agent process, PTY, or terminal forwarding. Representation adapters are best-effort compatibility rather than an OMP support guarantee. Alternate hosts and community contributors own validation of their compatibility shims and must supply every lifecycle semantic that the bridge requires.
|
|
100
|
+
|
|
91
101
|
### Guarded Invariants
|
|
92
102
|
|
|
93
103
|
Architecture invariant tests protect:
|
|
@@ -12,7 +12,7 @@ Every completed `assistant-segment` with `placement: "intermediate"` from a Tele
|
|
|
12
12
|
|
|
13
13
|
Proactive projection defaults on for local, autonomous, and unclassified extension follow-up Pi work. With `assistant.proactivePush` omitted or set to `true`, every completed public block—including visible commentary/checkpoints and the final answer—is projected to the instance's authorized target in source order; set it explicitly to `false` to opt out of only this local/autonomous projection. Both paths consume normalized complete Activity segments rather than raw token deltas, reasoning, or tool traffic.
|
|
14
14
|
|
|
15
|
-
Projected blocks use `assistant.rendering` independently of voice policy. Rich mode sends native Rich Markdown and HTML mode keeps the established HTML renderer; projection does not synthesize voice or attach queued files merely because Rich rendering is active. Ordered admission revalidates the exact target, profile/token transport generation, leader epoch or follower registration generation, and session generation before each send. Active-turn final delivery waits for admitted commentary inside its existing background delivery task, preserving commentary-before-final order without blocking Pi lifecycle completion. A `commit-unknown` outcome never permits replay.
|
|
15
|
+
Projected blocks use `assistant.rendering` independently of voice policy. Rich mode sends native Rich Markdown and HTML mode keeps the established HTML renderer; assistant-authored `telegram_button` comments are planned into prompt buttons before either renderer runs, while projection does not synthesize voice or attach queued files merely because Rich rendering is active. Ordered admission revalidates the exact target, profile/token transport generation, leader epoch or follower registration generation, and session generation before each send. Active-turn final delivery waits for admitted commentary inside its existing background delivery task, preserving commentary-before-final order without blocking Pi lifecycle completion. A `commit-unknown` outcome never permits replay.
|
|
16
16
|
|
|
17
17
|
## Technical Activity
|
|
18
18
|
|
|
@@ -40,8 +40,8 @@ import {
|
|
|
40
40
|
Stable commands inside Pi:
|
|
41
41
|
|
|
42
42
|
- `/telegram-setup` — configure/update the bot token.
|
|
43
|
-
- `/telegram-connect` — start polling here and acquire external Telegram control ownership. Accepted queue/reply state stays local if ownership later moves elsewhere.
|
|
44
|
-
- `/telegram-disconnect` — after destructive confirmation, stop polling and release ownership without deleting or silencing accepted local queue state. In Threaded Mode it deletes this instance's current Telegram thread; a follower waits for its active leader to confirm generation-fenced cleanup before stopping. Graceful Pi `quit` performs the same teardown without prompting, while `reload`, `new`, `resume`, and `fork` preserve same-process handoff.
|
|
43
|
+
- `/telegram-connect` — start polling here and acquire external Telegram control ownership. Accepted queue/reply state stays local if ownership later moves elsewhere. A successful command queues a hidden connection-state note for delivery with the agent's next turn without triggering one; it says Telegram is connected and that connectivity alone is not user intent.
|
|
44
|
+
- `/telegram-disconnect` — after destructive confirmation, stop polling and release ownership without deleting or silencing accepted local queue state. A successful command queues the corresponding hidden, non-triggering disconnected context note; cancelled or failed disconnects do not publish a false state transition. In Threaded Mode it deletes this instance's current Telegram thread; a follower waits for its active leader to confirm generation-fenced cleanup before stopping. Graceful Pi `quit` performs the same teardown without prompting, while `reload`, `new`, `resume`, and `fork` preserve same-process handoff.
|
|
45
45
|
- `/telegram-status` — show connection, polling, execution, queue, and recent event diagnostics; debug output separates poller and worker progress, durable automatic-retry state, exact foreign queued-owner identity, and negotiated protocol/build/capabilities.
|
|
46
46
|
|
|
47
47
|
### Telegram commands
|
|
@@ -168,22 +168,23 @@ Examples:
|
|
|
168
168
|
- `⚫️ Priority` / `🟣 Normal`
|
|
169
169
|
- `1` / `🟣 2` / `3`
|
|
170
170
|
|
|
171
|
-
##
|
|
171
|
+
## Option Lists
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
Option lists choose one value from a fixed set, for example model selection, thinking level, voice reply mode, or time injection mode.
|
|
174
174
|
|
|
175
175
|
Rules:
|
|
176
176
|
|
|
177
|
-
- Put each option on its own row.
|
|
177
|
+
- Put each option on its own row when labels are long, the set may grow, or scanning benefits from full width.
|
|
178
|
+
- A fixed set of short, ordered peer values may use compact rows of up to three buttons.
|
|
179
|
+
- Keep a semantically distinct value such as thinking `off` on its own full-width row before grouped intensity values.
|
|
178
180
|
- Mark only the current value with `🟢`.
|
|
179
181
|
- Leave non-current values without emoji.
|
|
180
182
|
- Use lowercase labels when the option is a value.
|
|
181
183
|
|
|
182
184
|
Examples:
|
|
183
185
|
|
|
184
|
-
- `hidden`
|
|
185
|
-
- `🟢
|
|
186
|
-
- `always`
|
|
186
|
+
- Vertical: `hidden`, `🟢 mirror`, `always`.
|
|
187
|
+
- Thinking: full-width `off`, then `minimal` / `low` / `🟢 medium`, then `high` / `xhigh` / `max`.
|
|
187
188
|
|
|
188
189
|
## Generated Prompt Buttons
|
|
189
190
|
|
|
@@ -6,10 +6,20 @@ Voice messages flow through an **inbound transcription → outbound voice reply*
|
|
|
6
6
|
|
|
7
7
|
1. **Inbound:** A voice message arrives via Telegram. Inbound handlers transcribe it to text.
|
|
8
8
|
2. **Processing:** The transcription becomes the agent prompt. The bridge tags the turn if it originated from voice.
|
|
9
|
-
3. **Outbound:**
|
|
9
|
+
3. **Outbound:** When automatic voice delivery is selected or the agent emits an explicit `telegram_voice` action, the agent's text is converted to voice and sent back.
|
|
10
10
|
|
|
11
11
|
The bridge owns Telegram transport, queue integration, reply-mode policy, preview suppression, fallback text delivery, and Settings UI. Provider extensions own STT/TTS calls, speech rewriting, provider-specific menus, transcript preference, and OGG/Opus conversion.
|
|
12
12
|
|
|
13
|
+
### Choose an integration path
|
|
14
|
+
|
|
15
|
+
Use the smallest path that fits the operator's available capabilities:
|
|
16
|
+
|
|
17
|
+
1. **`telegram.json` command templates:** Compose trusted local executables through `inboundHandlers` and `outboundHandlers`. This works equally well for local models and API-backed scripts, supports ordered STT fallbacks, and requires no companion extension.
|
|
18
|
+
2. **Companion extension:** Register programmatic STT/TTS providers when installation, provider-owned settings, lifecycle integration, or zero-config reuse justifies code.
|
|
19
|
+
3. **Hybrid:** Keep explicit operator command templates first and let installed providers supply progressive fallbacks.
|
|
20
|
+
|
|
21
|
+
pi-telegram does not catalog speech providers. Configuration agents should discover applicable Skills or trusted local executables, verify required environment variables by presence without exposing values, preserve unrelated `telegram.json` state, ensure TTS ends as OGG/Opus, and test each stage before the live Telegram path. `hidden` remains the safe and useful default: it disables only automatic voice replies, while explicit `telegram_voice` actions continue to use the configured synthesis pipeline.
|
|
22
|
+
|
|
13
23
|
## Voice Detection
|
|
14
24
|
|
|
15
25
|
Voice messages arrive as `message.voice` in Telegram updates. The bridge's media processing detects these and sets `kind: "voice"` on the downloaded file. Regular audio files (`message.audio`) get `kind: "audio"`; `mirror` mode treats both voice notes and audio uploads as voice input for reply-policy tagging.
|
|
@@ -539,6 +539,8 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
539
539
|
sendRichMessage,
|
|
540
540
|
editMessage: editTelegramMessageText,
|
|
541
541
|
getAssistantRenderingMode: configControls.getAssistantRenderingMode,
|
|
542
|
+
planButtonReply:
|
|
543
|
+
Outbound.createTelegramButtonReplyPlanner(buttonActionStore),
|
|
542
544
|
execCommand: CommandTemplates.execCommandTemplate,
|
|
543
545
|
getHandlers: configStore.getOutboundHandlers,
|
|
544
546
|
recordRuntimeEvent,
|
|
@@ -514,6 +514,18 @@ export function registerTelegramCommandsAndTools({
|
|
|
514
514
|
sendMarkdownReply(chatId, undefined, markdown, options),
|
|
515
515
|
recordRuntimeEvent,
|
|
516
516
|
});
|
|
517
|
+
const queueAgentConnectionContext = (connected: boolean): void => {
|
|
518
|
+
pi.sendMessage(
|
|
519
|
+
{
|
|
520
|
+
customType: "telegram-connection-state",
|
|
521
|
+
content: connected
|
|
522
|
+
? Prompts.TELEGRAM_CONNECTED_CONTEXT_MESSAGE
|
|
523
|
+
: Prompts.TELEGRAM_DISCONNECTED_CONTEXT_MESSAGE,
|
|
524
|
+
display: false,
|
|
525
|
+
},
|
|
526
|
+
{ deliverAs: "nextTurn" },
|
|
527
|
+
);
|
|
528
|
+
};
|
|
517
529
|
Commands.registerTelegramBridgeCommands(pi, {
|
|
518
530
|
promptForConfig: async (ctx, profileName) => {
|
|
519
531
|
const nextProfileName = profileName ?? undefined;
|
|
@@ -581,8 +593,11 @@ export function registerTelegramCommandsAndTools({
|
|
|
581
593
|
recordRuntimeEvent,
|
|
582
594
|
});
|
|
583
595
|
const completion = await runSetup(ctx);
|
|
584
|
-
if (
|
|
585
|
-
|
|
596
|
+
if (completion.status === "success") {
|
|
597
|
+
queueAgentConnectionContext(true);
|
|
598
|
+
if (profileName) {
|
|
599
|
+
ctx.ui.notify(`Profile "${profileName}" saved and connected.`, "info");
|
|
600
|
+
}
|
|
586
601
|
}
|
|
587
602
|
},
|
|
588
603
|
getStatusLines,
|
|
@@ -599,6 +614,7 @@ export function registerTelegramCommandsAndTools({
|
|
|
599
614
|
stopPolling: stopPolling ?? lockedPollingRuntime.stop,
|
|
600
615
|
recoverPollingStart,
|
|
601
616
|
getDisconnectThreadName,
|
|
617
|
+
queueAgentConnectionContext,
|
|
602
618
|
updateStatus,
|
|
603
619
|
getProfileNames: () =>
|
|
604
620
|
Config.getTelegramProfileNames(configStore.getStoredConfig()),
|
|
@@ -334,6 +334,7 @@ export interface TelegramBridgeCommandRegistrationDeps {
|
|
|
334
334
|
error: unknown,
|
|
335
335
|
) => Promise<TelegramPollingStartRecoveryResult>;
|
|
336
336
|
getDisconnectThreadName?: () => string | undefined;
|
|
337
|
+
queueAgentConnectionContext?: (connected: boolean) => void;
|
|
337
338
|
updateStatus: (ctx: ExtensionCommandContext) => void;
|
|
338
339
|
getProfileNames?: () => string[];
|
|
339
340
|
activateDefaultProfileConfig?: (ctx: ExtensionCommandContext) => Promise<void>;
|
|
@@ -468,6 +469,9 @@ export function registerTelegramBridgeCommands(
|
|
|
468
469
|
if (result?.message) {
|
|
469
470
|
ctx.ui.notify(result.message, result.ok ? "info" : "warning");
|
|
470
471
|
}
|
|
472
|
+
if (!result || result.ok) {
|
|
473
|
+
deps.queueAgentConnectionContext?.(true);
|
|
474
|
+
}
|
|
471
475
|
deps.updateStatus(ctx);
|
|
472
476
|
},
|
|
473
477
|
});
|
|
@@ -490,6 +494,7 @@ export function registerTelegramBridgeCommands(
|
|
|
490
494
|
try {
|
|
491
495
|
const message = await deps.stopPolling();
|
|
492
496
|
if (message) ctx.ui.notify(message, "info");
|
|
497
|
+
deps.queueAgentConnectionContext?.(false);
|
|
493
498
|
} catch (error) {
|
|
494
499
|
const detail = error instanceof Error ? error.message : String(error);
|
|
495
500
|
ctx.ui.notify(
|
|
@@ -871,21 +871,14 @@ export function registerTelegramBindTool(
|
|
|
871
871
|
label: "Telegram Bind",
|
|
872
872
|
description:
|
|
873
873
|
"Install, explicitly replace, or invoke one named method on a managed Generative App; successful output displays directly in the active Telegram turn unless display is false.",
|
|
874
|
-
parameters: Type.
|
|
875
|
-
Type.
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
Type.Object({
|
|
883
|
-
app: Type.String(),
|
|
884
|
-
method: Type.String(),
|
|
885
|
-
display: Type.Optional(Type.Boolean()),
|
|
886
|
-
argument: Type.Optional(Type.Unknown()),
|
|
887
|
-
}, { additionalProperties: false }),
|
|
888
|
-
]),
|
|
874
|
+
parameters: Type.Object({
|
|
875
|
+
app: Type.String(),
|
|
876
|
+
script: Type.Optional(Type.String()),
|
|
877
|
+
method: Type.Optional(Type.String()),
|
|
878
|
+
replace: Type.Optional(Type.Boolean()),
|
|
879
|
+
display: Type.Optional(Type.Boolean()),
|
|
880
|
+
argument: Type.Optional(Type.Unknown()),
|
|
881
|
+
}, { additionalProperties: false }),
|
|
889
882
|
async execute(_toolCallId, params) {
|
|
890
883
|
try {
|
|
891
884
|
const result = await bindGenerativeApp({
|
|
@@ -1757,6 +1757,30 @@ export function createTelegramUpdateJournalStore(
|
|
|
1757
1757
|
} catch (error) {
|
|
1758
1758
|
if (error instanceof TelegramUpdateJournalError) throw error;
|
|
1759
1759
|
if ((error as { code?: unknown })?.code === "ENOENT") {
|
|
1760
|
+
const segmentDirectory = getTelegramUpdateJournalSegmentDirectory(path);
|
|
1761
|
+
let orphanedSegmentNames: string[];
|
|
1762
|
+
try {
|
|
1763
|
+
orphanedSegmentNames = readdirSync(segmentDirectory).filter((name) =>
|
|
1764
|
+
/^\d{16}\.json$/u.test(name),
|
|
1765
|
+
);
|
|
1766
|
+
} catch (segmentError) {
|
|
1767
|
+
if ((segmentError as { code?: unknown })?.code === "ENOENT") {
|
|
1768
|
+
return { file: emptyFile(), exists: false, serializedBytes: 0 };
|
|
1769
|
+
}
|
|
1770
|
+
throw createJournalError(
|
|
1771
|
+
"io",
|
|
1772
|
+
segmentDirectory,
|
|
1773
|
+
"could not be read while the journal snapshot is missing",
|
|
1774
|
+
segmentError,
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
if (orphanedSegmentNames.length > 0) {
|
|
1778
|
+
throw createJournalError(
|
|
1779
|
+
"invalid",
|
|
1780
|
+
path,
|
|
1781
|
+
`is missing while ${segmentDirectory} retains revision segments`,
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1760
1784
|
return { file: emptyFile(), exists: false, serializedBytes: 0 };
|
|
1761
1785
|
}
|
|
1762
1786
|
throw createJournalError("io", path, "could not be read", error);
|
|
@@ -40,8 +40,15 @@ export function createAgentStartDedupHook(
|
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
type TelegramBeforeAgentStartEvent = Omit<
|
|
44
|
+
BeforeAgentStartEvent,
|
|
45
|
+
"systemPrompt"
|
|
46
|
+
> & {
|
|
47
|
+
systemPrompt: string | string[];
|
|
48
|
+
};
|
|
49
|
+
|
|
43
50
|
export interface TelegramBeforeAgentStartResult {
|
|
44
|
-
systemPrompt?: string;
|
|
51
|
+
systemPrompt?: string | string[];
|
|
45
52
|
}
|
|
46
53
|
|
|
47
54
|
type TelegramBeforeAgentStartReturn =
|
|
@@ -72,7 +79,7 @@ export interface TelegramLifecycleRegistrationDeps {
|
|
|
72
79
|
ctx: ExtensionContext,
|
|
73
80
|
) => Promise<void> | void;
|
|
74
81
|
onBeforeAgentStart: (
|
|
75
|
-
event:
|
|
82
|
+
event: TelegramBeforeAgentStartEvent,
|
|
76
83
|
ctx: ExtensionContext,
|
|
77
84
|
) => TelegramBeforeAgentStartReturn;
|
|
78
85
|
onModelSelect: (
|
|
@@ -581,7 +588,16 @@ export function registerTelegramLifecycleHooks(
|
|
|
581
588
|
if (!isActive(ctx)) return;
|
|
582
589
|
await deps.onSessionCompact?.(event, ctx);
|
|
583
590
|
});
|
|
584
|
-
|
|
591
|
+
// The Pi SDK still types this result as a string; compatible runtimes may
|
|
592
|
+
// preserve ordered system prompt blocks through the same public hook.
|
|
593
|
+
const registerBeforeAgentStart = pi.on.bind(pi) as unknown as (
|
|
594
|
+
event: "before_agent_start",
|
|
595
|
+
handler: (
|
|
596
|
+
event: TelegramBeforeAgentStartEvent,
|
|
597
|
+
ctx: ExtensionContext,
|
|
598
|
+
) => TelegramBeforeAgentStartReturn,
|
|
599
|
+
) => void;
|
|
600
|
+
registerBeforeAgentStart("before_agent_start", async (event, ctx) => {
|
|
585
601
|
return deps.onBeforeAgentStart(event, ctx);
|
|
586
602
|
});
|
|
587
603
|
pi.on("model_select", async (event, ctx) => {
|
|
@@ -119,7 +119,9 @@ export interface TelegramModelMenuStateBuilderDeps<
|
|
|
119
119
|
TelegramModelMenuStateBuilderContext<TModel>,
|
|
120
120
|
> {
|
|
121
121
|
runtime: TelegramModelMenuRuntime<TModel>;
|
|
122
|
-
createSettingsManager: (
|
|
122
|
+
createSettingsManager: (
|
|
123
|
+
cwd: string,
|
|
124
|
+
) => MenuSettingsManager | PromiseLike<MenuSettingsManager>;
|
|
123
125
|
getActiveModel: (ctx: TContext) => TModel | undefined;
|
|
124
126
|
}
|
|
125
127
|
|
|
@@ -490,7 +492,7 @@ export function createTelegramModelMenuStateBuilder<
|
|
|
490
492
|
threadId?: number,
|
|
491
493
|
) => Promise<TelegramModelMenuState<TModel>> {
|
|
492
494
|
return async (chatId, ctx, threadId) => {
|
|
493
|
-
const settingsManager = deps.createSettingsManager(ctx.cwd);
|
|
495
|
+
const settingsManager = await deps.createSettingsManager(ctx.cwd);
|
|
494
496
|
return deps.runtime.buildState({
|
|
495
497
|
chatId,
|
|
496
498
|
threadId,
|
|
@@ -128,13 +128,14 @@ export function buildThinkingMenuReplyMarkup(
|
|
|
128
128
|
currentThinkingLevel: ThinkingLevel,
|
|
129
129
|
): TelegramReplyMarkup {
|
|
130
130
|
const rows = [[{ text: "⬆️ Main menu", callback_data: "menu:back" }]];
|
|
131
|
+
const levelButtons = THINKING_LEVELS.map((level) => ({
|
|
132
|
+
text: level === currentThinkingLevel ? `🟢 ${level}` : level,
|
|
133
|
+
callback_data: `thinking:set:${level}`,
|
|
134
|
+
}));
|
|
131
135
|
rows.push(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
callback_data: `thinking:set:${level}`,
|
|
136
|
-
},
|
|
137
|
-
]),
|
|
136
|
+
levelButtons.slice(0, 1),
|
|
137
|
+
levelButtons.slice(1, 4),
|
|
138
|
+
levelButtons.slice(4, 7),
|
|
138
139
|
);
|
|
139
140
|
return { inline_keyboard: rows };
|
|
140
141
|
}
|
|
@@ -436,6 +436,11 @@ async function buildTelegramOutboundAttachmentViews(options: {
|
|
|
436
436
|
return pendingAttachments;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
function formatTelegramOutboundToolError(error: unknown): Error {
|
|
440
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
441
|
+
return new Error(`\n${message.replace(/^\n+/u, "") || "Telegram outbound operation failed."}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
439
444
|
export function registerTelegramOutboundAttachmentTool(
|
|
440
445
|
pi: ExtensionAPI,
|
|
441
446
|
deps: TelegramOutboundAttachmentToolRegistrationDeps,
|
|
@@ -496,7 +501,7 @@ export function registerTelegramOutboundAttachmentTool(
|
|
|
496
501
|
phase: "queue",
|
|
497
502
|
count: params.paths.length,
|
|
498
503
|
});
|
|
499
|
-
throw error;
|
|
504
|
+
throw formatTelegramOutboundToolError(error);
|
|
500
505
|
}
|
|
501
506
|
},
|
|
502
507
|
});
|
|
@@ -548,7 +553,7 @@ export function registerTelegramOutboundMessageTool(
|
|
|
548
553
|
});
|
|
549
554
|
} catch (error) {
|
|
550
555
|
deps.recordRuntimeEvent?.("message", error, { phase: "direct" });
|
|
551
|
-
throw error;
|
|
556
|
+
throw formatTelegramOutboundToolError(error);
|
|
552
557
|
}
|
|
553
558
|
},
|
|
554
559
|
});
|
|
@@ -958,6 +958,10 @@ export function createTelegramAssistantOutputSender<
|
|
|
958
958
|
) => Promise<TelegramSentMessage>;
|
|
959
959
|
editMessage: (body: TelegramEditMessageTextBody) => Promise<unknown>;
|
|
960
960
|
getAssistantRenderingMode: () => "rich" | "html";
|
|
961
|
+
planButtonReply?: (markdown: string) => {
|
|
962
|
+
markdown: string;
|
|
963
|
+
replyMarkup?: TReplyMarkup;
|
|
964
|
+
};
|
|
961
965
|
execCommand: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>["execCommand"];
|
|
962
966
|
getHandlers?: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>["getHandlers"];
|
|
963
967
|
recordRuntimeEvent?: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>["recordRuntimeEvent"];
|
|
@@ -997,11 +1001,19 @@ export function createTelegramAssistantOutputSender<
|
|
|
997
1001
|
getHandlers: deps.getHandlers,
|
|
998
1002
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
999
1003
|
});
|
|
1004
|
+
const buttonReply = deps.planButtonReply?.(event.text) ?? {
|
|
1005
|
+
markdown: event.text,
|
|
1006
|
+
};
|
|
1000
1007
|
await outboundRuntime.sendMarkdownReply(
|
|
1001
1008
|
target.chatId,
|
|
1002
1009
|
undefined,
|
|
1003
|
-
|
|
1004
|
-
{
|
|
1010
|
+
buttonReply.markdown,
|
|
1011
|
+
{
|
|
1012
|
+
target,
|
|
1013
|
+
...(buttonReply.replyMarkup
|
|
1014
|
+
? { replyMarkup: buttonReply.replyMarkup }
|
|
1015
|
+
: {}),
|
|
1016
|
+
},
|
|
1005
1017
|
);
|
|
1006
1018
|
};
|
|
1007
1019
|
}
|
|
@@ -151,16 +151,82 @@ export function createExtensionApiRuntimePorts(
|
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
type PiSettingsManagerFactory = {
|
|
155
|
+
create: (cwd: string) => unknown | PromiseLike<unknown>;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
type HostSettingsManager = {
|
|
159
|
+
reload?: () => void | PromiseLike<void>;
|
|
160
|
+
flush?: () => void | PromiseLike<void>;
|
|
161
|
+
getEnabledModels?: () => unknown;
|
|
162
|
+
setEnabledModels?: (patterns: string[] | undefined) => void;
|
|
163
|
+
get?: (key: string) => unknown;
|
|
164
|
+
set?: (key: string, value: unknown) => void;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function readEnabledModels(value: unknown): string[] | undefined {
|
|
168
|
+
if (value === undefined) return undefined;
|
|
169
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
170
|
+
return [...value];
|
|
171
|
+
}
|
|
172
|
+
throw new TypeError("Host settings enabledModels must be a string array or undefined.");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function normalizeSettingsManager(manager: unknown): PiSettingsManager {
|
|
176
|
+
if (typeof manager !== "object" || manager === null) {
|
|
177
|
+
throw new TypeError("Host settings manager must be an object.");
|
|
178
|
+
}
|
|
179
|
+
const host = manager as HostSettingsManager;
|
|
180
|
+
if (typeof host.flush !== "function") {
|
|
181
|
+
throw new TypeError("Host settings manager must provide flush().");
|
|
182
|
+
}
|
|
183
|
+
const read = typeof host.getEnabledModels === "function"
|
|
184
|
+
? () => host.getEnabledModels!.call(host)
|
|
185
|
+
: typeof host.get === "function"
|
|
186
|
+
? () => host.get!.call(host, "enabledModels")
|
|
187
|
+
: undefined;
|
|
188
|
+
const write = typeof host.setEnabledModels === "function"
|
|
189
|
+
? (patterns: string[] | undefined) =>
|
|
190
|
+
host.setEnabledModels!.call(host, patterns)
|
|
191
|
+
: typeof host.set === "function"
|
|
192
|
+
? (patterns: string[] | undefined) =>
|
|
193
|
+
host.set!.call(host, "enabledModels", patterns ?? [])
|
|
194
|
+
: undefined;
|
|
195
|
+
if (!read || !write) {
|
|
196
|
+
throw new TypeError(
|
|
197
|
+
"Host settings manager must provide enabled-model read and write capabilities.",
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
reload: async () => {
|
|
202
|
+
await host.reload?.call(host);
|
|
203
|
+
},
|
|
204
|
+
flush: async () => {
|
|
205
|
+
await host.flush!.call(host);
|
|
206
|
+
},
|
|
207
|
+
getEnabledModels: () => readEnabledModels(read()),
|
|
208
|
+
setEnabledModels: write,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function createSettingsManager(
|
|
213
|
+
cwd: string,
|
|
214
|
+
): Promise<PiSettingsManager> {
|
|
215
|
+
// Pi returns its legacy settings surface synchronously. Compatible hosts may
|
|
216
|
+
// resolve a generic settings service asynchronously; normalize both once at
|
|
217
|
+
// the SDK boundary instead of leaking host distinctions into menu domains.
|
|
218
|
+
const factory = SettingsManager as unknown as PiSettingsManagerFactory;
|
|
219
|
+
return normalizeSettingsManager(await factory.create(cwd));
|
|
156
220
|
}
|
|
157
221
|
|
|
158
222
|
export function createScopedModelPatternPersister(deps: {
|
|
159
|
-
createSettingsManager: (
|
|
223
|
+
createSettingsManager: (
|
|
224
|
+
cwd: string,
|
|
225
|
+
) => PiSettingsManager | PromiseLike<PiSettingsManager>;
|
|
160
226
|
clearCachedModelMenuInputs: () => void;
|
|
161
227
|
}): (patterns: string[], ctx: ExtensionContext) => Promise<void> {
|
|
162
228
|
return async (patterns, ctx) => {
|
|
163
|
-
const settingsManager = deps.createSettingsManager(ctx.cwd);
|
|
229
|
+
const settingsManager = await deps.createSettingsManager(ctx.cwd);
|
|
164
230
|
settingsManager.setEnabledModels(
|
|
165
231
|
patterns.length > 0 ? patterns : undefined,
|
|
166
232
|
);
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
import type { BeforeAgentStartEvent } from "./pi.ts";
|
|
8
8
|
import { TELEGRAM_PREFIX } from "./turns.ts";
|
|
9
9
|
|
|
10
|
+
export const TELEGRAM_CONNECTED_CONTEXT_MESSAGE =
|
|
11
|
+
"Telegram session connected. Use Telegram features for Telegram-originated turns or explicit Telegram requests; connectivity alone is not user intent.";
|
|
12
|
+
export const TELEGRAM_DISCONNECTED_CONTEXT_MESSAGE =
|
|
13
|
+
"Telegram session disconnected. Do not use Telegram delivery, actions, or Telegram-specific reply features unless the user reconnects it.";
|
|
14
|
+
|
|
10
15
|
const LOCAL_SYSTEM_PROMPT_SUFFIX = `
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
${TELEGRAM_CONNECTED_CONTEXT_MESSAGE} Load the \`telegram-bridge\` Skill for Telegram-originated turns or explicit requests involving Telegram delivery, actions, Threaded Mode, or diagnosis. Do not use Telegram-specific features from unrelated local/TUI prompts.`;
|
|
13
18
|
|
|
14
19
|
const TELEGRAM_TURN_SYSTEM_PROMPT_SUFFIX = `
|
|
15
20
|
|
|
@@ -31,6 +36,15 @@ export const TELEGRAM_MESSAGE_PROMPT_GUIDELINES = [
|
|
|
31
36
|
"During an active Telegram turn, omit telegram_message for the current target and answer normally; use thread only when the user requests delivery to a different live Pi thread.",
|
|
32
37
|
] as const;
|
|
33
38
|
|
|
39
|
+
const TELEGRAM_TOOL_METADATA_LINES = Object.fromEntries(
|
|
40
|
+
[
|
|
41
|
+
`- telegram_attach: ${TELEGRAM_ATTACH_PROMPT_SNIPPET}`,
|
|
42
|
+
`- telegram_message: ${TELEGRAM_MESSAGE_PROMPT_SNIPPET}`,
|
|
43
|
+
...TELEGRAM_ATTACH_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
44
|
+
...TELEGRAM_MESSAGE_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
45
|
+
].map((line) => [line, true]),
|
|
46
|
+
) as Record<string, true>;
|
|
47
|
+
|
|
34
48
|
const TELEGRAM_MODEL_CONTEXT_TOOL_NAMES = new Set([
|
|
35
49
|
"telegram_attach",
|
|
36
50
|
"telegram_bind",
|
|
@@ -132,13 +146,30 @@ export function createTelegramModelContextAvailabilityRuntime(deps: {
|
|
|
132
146
|
};
|
|
133
147
|
}
|
|
134
148
|
|
|
149
|
+
export type TelegramSystemPrompt = string | string[];
|
|
150
|
+
|
|
151
|
+
type TelegramBeforeAgentStartEvent = Omit<
|
|
152
|
+
BeforeAgentStartEvent,
|
|
153
|
+
"systemPrompt"
|
|
154
|
+
> & {
|
|
155
|
+
systemPrompt: TelegramSystemPrompt;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
type TelegramBeforeAgentStartResult = {
|
|
159
|
+
systemPrompt: TelegramSystemPrompt;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
type TelegramBeforeAgentStartHook = (
|
|
163
|
+
event: TelegramBeforeAgentStartEvent,
|
|
164
|
+
) => TelegramBeforeAgentStartResult;
|
|
165
|
+
|
|
135
166
|
export function buildTelegramBridgeSystemPrompt(options: {
|
|
136
167
|
prompt: string;
|
|
137
|
-
systemPrompt:
|
|
168
|
+
systemPrompt: TelegramSystemPrompt;
|
|
138
169
|
telegramPrefix?: string;
|
|
139
170
|
localSystemPromptSuffix: string;
|
|
140
171
|
telegramTurnSystemPromptSuffix: string;
|
|
141
|
-
}):
|
|
172
|
+
}): TelegramBeforeAgentStartResult {
|
|
142
173
|
const telegramPrefix = options.telegramPrefix ?? TELEGRAM_PREFIX;
|
|
143
174
|
const telegramHead = telegramPrefix.endsWith("]")
|
|
144
175
|
? telegramPrefix.slice(0, -1)
|
|
@@ -151,8 +182,14 @@ export function buildTelegramBridgeSystemPrompt(options: {
|
|
|
151
182
|
? `${options.telegramTurnSystemPromptSuffix}\n- The current user message came from Telegram.`
|
|
152
183
|
: "";
|
|
153
184
|
return {
|
|
154
|
-
systemPrompt:
|
|
155
|
-
|
|
185
|
+
systemPrompt: Array.isArray(options.systemPrompt)
|
|
186
|
+
? [
|
|
187
|
+
...options.systemPrompt,
|
|
188
|
+
options.localSystemPromptSuffix + telegramSuffix,
|
|
189
|
+
]
|
|
190
|
+
: options.systemPrompt +
|
|
191
|
+
options.localSystemPromptSuffix +
|
|
192
|
+
telegramSuffix,
|
|
156
193
|
};
|
|
157
194
|
}
|
|
158
195
|
|
|
@@ -162,7 +199,7 @@ export function createTelegramBeforeAgentStartHook(
|
|
|
162
199
|
localSystemPromptSuffix?: string;
|
|
163
200
|
telegramTurnSystemPromptSuffix?: string;
|
|
164
201
|
} = {},
|
|
165
|
-
):
|
|
202
|
+
): TelegramBeforeAgentStartHook {
|
|
166
203
|
return (event) =>
|
|
167
204
|
buildTelegramBridgeSystemPrompt({
|
|
168
205
|
prompt: event.prompt,
|
|
@@ -176,23 +213,23 @@ export function createTelegramBeforeAgentStartHook(
|
|
|
176
213
|
});
|
|
177
214
|
}
|
|
178
215
|
|
|
179
|
-
function
|
|
180
|
-
systemPrompt: string,
|
|
181
|
-
): string {
|
|
182
|
-
const telegramLines = new Set([
|
|
183
|
-
`- telegram_attach: ${TELEGRAM_ATTACH_PROMPT_SNIPPET}`,
|
|
184
|
-
`- telegram_message: ${TELEGRAM_MESSAGE_PROMPT_SNIPPET}`,
|
|
185
|
-
...TELEGRAM_ATTACH_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
186
|
-
...TELEGRAM_MESSAGE_PROMPT_GUIDELINES.map((line) => `- ${line}`),
|
|
187
|
-
]);
|
|
216
|
+
function stripTelegramToolMetadataFromString(systemPrompt: string): string {
|
|
188
217
|
return systemPrompt
|
|
189
218
|
.split("\n")
|
|
190
|
-
.filter((line) =>
|
|
219
|
+
.filter((line) => TELEGRAM_TOOL_METADATA_LINES[line] !== true)
|
|
191
220
|
.join("\n");
|
|
192
221
|
}
|
|
193
222
|
|
|
223
|
+
function stripTelegramToolMetadataFromSystemPrompt(
|
|
224
|
+
systemPrompt: TelegramSystemPrompt,
|
|
225
|
+
): TelegramSystemPrompt {
|
|
226
|
+
return Array.isArray(systemPrompt)
|
|
227
|
+
? systemPrompt.map(stripTelegramToolMetadataFromString)
|
|
228
|
+
: stripTelegramToolMetadataFromString(systemPrompt);
|
|
229
|
+
}
|
|
230
|
+
|
|
194
231
|
export interface TelegramProactivePromptHookDeps<TContext> {
|
|
195
|
-
baseHook?:
|
|
232
|
+
baseHook?: TelegramBeforeAgentStartHook;
|
|
196
233
|
reconcileAvailability?: () => void;
|
|
197
234
|
isAvailable: (ctx: TContext) => boolean;
|
|
198
235
|
}
|
|
@@ -200,9 +237,9 @@ export interface TelegramProactivePromptHookDeps<TContext> {
|
|
|
200
237
|
export function createTelegramProactiveBeforeAgentStartHook<TContext>(
|
|
201
238
|
deps: TelegramProactivePromptHookDeps<TContext>,
|
|
202
239
|
): (
|
|
203
|
-
event:
|
|
240
|
+
event: TelegramBeforeAgentStartEvent,
|
|
204
241
|
ctx: TContext,
|
|
205
|
-
) => Promise<
|
|
242
|
+
) => Promise<TelegramBeforeAgentStartResult> {
|
|
206
243
|
const baseHook = deps.baseHook ?? createTelegramBeforeAgentStartHook();
|
|
207
244
|
return async (event, ctx) => {
|
|
208
245
|
deps.reconcileAvailability?.();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: generated-control-surface
|
|
3
|
-
description: Proactively compiles current state, available capabilities, and user intent into contextual, evidence-backed, ephemeral prompt-button interfaces. Use
|
|
3
|
+
description: Proactively compiles current state, available capabilities, and user intent into contextual, evidence-backed, ephemeral prompt-button interfaces. Use without an explicit button request on Telegram-originated turns when controls materially shorten likely feedback, or when a user explicitly requests controls on another prompt-button transport; connection or proactive projection alone is not activation intent. Route reusable deterministic loops toward Generative Apps, omit decorative UI, and preserve domain ownership while fixed transport menus and callbacks remain with their runtime owners.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Generated Control Surface
|
|
@@ -29,7 +29,7 @@ This Skill and `generative-apps` share one logical button-matrix and `label + pr
|
|
|
29
29
|
|
|
30
30
|
When a generated surface reveals repeated stable interaction with bounded state and deterministic transitions, load the complementary `generative-apps` Skill and consider compiling that loop. A Generative App may retain ordinary model-mediated prompt buttons beside deterministic bound methods, so only the stable actions need bypass inference. Conversely, keep one-off, interpretive, changing, and context-heavy interaction here when a reusable script would add no concrete latency, token, cost, reliability, or UX value.
|
|
31
31
|
|
|
32
|
-
On Telegram turns, evaluate this Skill proactively rather than waiting for the user to ask for buttons. Load and apply it when a likely next decision, approval, navigation step, inspection, or bounded action can be made materially easier through controls; its correct output may still contain zero buttons when the admission test fails.
|
|
32
|
+
On Telegram-originated turns, evaluate this Skill proactively rather than waiting for the user to ask for buttons. A connected Telegram session, available button syntax, or proactive projection of local output is capability evidence, not activation intent: do not load this Skill for an unrelated local/TUI prompt merely because its answer may also appear in Telegram. Load and apply it when the current turn is Telegram-originated or the user explicitly requests a prompt-button surface and a likely next decision, approval, navigation step, inspection, or bounded action can be made materially easier through controls; its correct output may still contain zero buttons when the admission test fails.
|
|
33
33
|
|
|
34
34
|
A control surface may expose:
|
|
35
35
|
|
|
@@ -7,6 +7,12 @@ description: Operates pi-telegram turns, replies, attachments, direct delivery,
|
|
|
7
7
|
|
|
8
8
|
Use pi-telegram as a mobile companion surface for the current Pi session. Preserve the current Telegram target, ordinary reply ownership, durable queue semantics, and the boundary between agent intent and bridge transport.
|
|
9
9
|
|
|
10
|
+
## Connection Awareness
|
|
11
|
+
|
|
12
|
+
`Telegram session connected.` means this Pi instance currently has authorized Telegram transport, so Telegram tools and proactive projection may be available. It does not mean the current prompt came from Telegram and does not grant intent to add Telegram actions to unrelated local/TUI work. Use Telegram-specific reply features proactively only on a turn carrying structured Telegram origin; from local/TUI prompts, use them only when the user explicitly requests Telegram delivery or the established workflow is explicitly Telegram-mediated.
|
|
13
|
+
|
|
14
|
+
`Telegram session disconnected.` revokes that availability. Do not attempt Telegram delivery, actions, buttons, or voice until a later connected context is present. Treat the newest connection-state context as authoritative.
|
|
15
|
+
|
|
10
16
|
## Turn Recognition
|
|
11
17
|
|
|
12
18
|
Telegram-originated prompts carry structured context:
|
|
@@ -46,9 +52,9 @@ Use `telegram_attach` outside Telegram turns only when the user explicitly reque
|
|
|
46
52
|
|
|
47
53
|
## Assistant-Authored Actions
|
|
48
54
|
|
|
49
|
-
Before compiling assistant-authored controls, inspect already-loaded capability guidance for an advertised maintained Generative App or view/controller adapter. When that owner-provided view exists and the current intent concerns repeated interaction, load and follow the bundled `generative-apps` Skill and prefer binding or invoking the existing app over synthesizing one-shot prompt buttons. This
|
|
55
|
+
Before compiling assistant-authored controls, inspect already-loaded capability guidance for an advertised maintained Generative App or view/controller adapter. When that owner-provided view exists and the current intent concerns repeated interaction, load and follow the bundled `generative-apps` Skill and prefer binding or invoking the existing app over synthesizing one-shot prompt buttons. This routing guidance is not permission for the bridge to discover capability-specific apps, own their state, or hard-code their identities.
|
|
50
56
|
|
|
51
|
-
Otherwise, on Telegram turns, proactively load `generated-control-surface` when a likely next decision or action may benefit from prompt buttons; do not wait for an explicit button request, and accept zero controls when its admission rules reject decorative or low-value UI.
|
|
57
|
+
Otherwise, only on Telegram-originated turns, proactively load `generated-control-surface` when a likely next decision or action may benefit from prompt buttons; do not activate it merely because Telegram is connected or local output may be projected by proactive push. Do not wait for an explicit button request on a Telegram-originated turn, and accept zero controls when its admission rules reject decorative or low-value UI.
|
|
52
58
|
|
|
53
59
|
`telegram_button` and `telegram_voice` are hidden top-level HTML comments, not tools. Emit them at column zero, outside lists, quotes, code blocks, and indentation.
|
|
54
60
|
|
|
@@ -116,6 +122,8 @@ Prefer no-code command-template configuration in `telegram.json` before adding a
|
|
|
116
122
|
- `outboundHandlers` transforms final replies.
|
|
117
123
|
- Voice transcription handlers can match `type: "voice"` or `mime: "audio/*"`; stdout becomes `[outputs]`.
|
|
118
124
|
|
|
125
|
+
When asked to configure voice rather than merely operate it, follow the provider-neutral contracts in `docs/voice.md`, `docs/inbound.md`, `docs/outbound.md`, and `docs/command-templates.md` from the pi-telegram package or repository. Inspect the available Skill catalog and trusted local executables for STT, TTS, and media conversion capabilities; check only whether required environment variables exist, never reveal their values. Preserve unrelated `telegram.json` fields, order multiple matching inbound handlers as fallbacks, require OGG/Opus output for native voice delivery, and validate each stage before a live Telegram smoke test. Keep `voice.replyMode` at its existing value unless the user requests a policy change: the default `hidden` mode is fully functional because explicit top-level `telegram_voice` actions still use the configured synthesis pipeline.
|
|
126
|
+
|
|
119
127
|
When configuration is insufficient, use documented `@llblab/pi-telegram/*` public API subpaths. Never import package-private `lib/*`, start another polling loop, or bypass bridge ownership with raw Bot API access.
|
|
120
128
|
|
|
121
129
|
## Safety
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@llblab/pi-actors": "0.49.1",
|
|
44
44
|
"@llblab/pi-codex-usage": "0.9.2",
|
|
45
45
|
"@llblab/pi-grow-loop": "0.7.1",
|
|
46
|
-
"@llblab/pi-telegram": "0.36.
|
|
46
|
+
"@llblab/pi-telegram": "0.36.7"
|
|
47
47
|
},
|
|
48
48
|
"bundledDependencies": [
|
|
49
49
|
"@llblab/pi-actors",
|
|
@@ -63,5 +63,11 @@
|
|
|
63
63
|
"./node_modules/@llblab/pi-grow-loop/skills",
|
|
64
64
|
"./node_modules/@llblab/pi-telegram/skills"
|
|
65
65
|
]
|
|
66
|
-
}
|
|
66
|
+
},
|
|
67
|
+
"bundleDependencies": [
|
|
68
|
+
"@llblab/pi-actors",
|
|
69
|
+
"@llblab/pi-codex-usage",
|
|
70
|
+
"@llblab/pi-grow-loop",
|
|
71
|
+
"@llblab/pi-telegram"
|
|
72
|
+
]
|
|
67
73
|
}
|