@llblab/pi-telegram 0.36.5 → 0.36.7

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
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
+
5
15
  ## 0.36.5: Separated Tool Errors
6
16
 
7
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.
@@ -28,7 +38,7 @@
28
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.
29
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.
30
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.
31
- - `Hybrid Surface Breadcrumbs`: 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.
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.
32
42
 
33
43
  ## 0.36.0: Generative Apps
34
44
 
package/README.md CHANGED
@@ -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
 
@@ -218,7 +218,7 @@ Inbound files land under `<agent-dir>/tmp/telegram` and default to a 50 MiB limi
218
218
 
219
219
  ### Voice And Media
220
220
 
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. Explicit `telegram_voice` actions accept either a JSON object or compact double-quoted attributes, with equivalent `text` and `value` payload keys and one colon-free action marker.
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.
222
222
 
223
223
  ### Buttons And Callbacks
224
224
 
package/docs/outbound.md CHANGED
@@ -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
package/docs/voice.md CHANGED
@@ -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:** If voice replies are enabled, the agent's text response is converted to voice and sent back. No text draft appears in Telegram during generation.
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.
package/index.ts CHANGED
@@ -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,
package/lib/bindings.ts CHANGED
@@ -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 (profileName && completion.status === "success") {
585
- ctx.ui.notify(`Profile "${profileName}" saved and connected.`, "info");
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()),
package/lib/commands.ts CHANGED
@@ -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(
package/lib/journal.ts CHANGED
@@ -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);
package/lib/outbound.ts CHANGED
@@ -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
- event.text,
1004
- { target },
1010
+ buttonReply.markdown,
1011
+ {
1012
+ target,
1013
+ ...(buttonReply.replyMarkup
1014
+ ? { replyMarkup: buttonReply.replyMarkup }
1015
+ : {}),
1016
+ },
1005
1017
  );
1006
1018
  };
1007
1019
  }
package/lib/prompts.ts CHANGED
@@ -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
- Telegram bridge available. Load the \`telegram-bridge\` Skill for Telegram delivery, actions, Threaded Mode, or diagnosis. Do not use Telegram from local/TUI prompts unless explicitly asked.`;
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.36.5",
3
+ "version": "0.36.7",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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 on Telegram turns and other prompt-button transports whenever controls materially shorten likely feedback, without requiring an explicit user request; 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.
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 is a routing breadcrumb, not permission for the bridge to discover capability-specific apps, own their state, or hard-code their identities.
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