@llblab/pi-telegram 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +7 -1
- package/api/status.ts +12 -0
- package/docs/public-api.md +25 -0
- package/lib/status.ts +88 -3
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
No open changes.
|
|
6
6
|
|
|
7
|
+
## 0.15.0: Companion Status Lines
|
|
8
|
+
|
|
9
|
+
- `[API]` Added `registerTelegramStatusLineProvider()` on the public `/status` subpath so companion extensions can append compact rows to the `/start` menu status text. Providers are synchronous, model-aware, isolated on failure, and rendered with Telegram-style capitalized labels. Impact: quota/status widgets can progressively enhance the Telegram operator menu without owning polling, transport, or core menu rendering.
|
|
10
|
+
- `[Docs]` Documented the status-line provider with an abstract companion-extension example and listed `pi-codex-usage` as a companion extension. Impact: the public API docs stay implementation-neutral while the README still points operators to the concrete Codex quota widget.
|
|
11
|
+
|
|
7
12
|
## 0.14.0: Direct Telegram Delivery, Queue Semantics, And Section Diagnostics
|
|
8
13
|
|
|
9
14
|
- `[Prompt Guidance]` Tightened agent context for Telegram buttons: use normal Markdown plus top-level hidden `telegram_button` comments, never JSON button specs or standalone button actions, and keep comments out of code/quotes/lists/indented examples. Impact: agents immediately know how to author visible Telegram text, inline buttons, and direct `telegram_message` payloads without transport hacks.
|
package/README.md
CHANGED
|
@@ -209,7 +209,7 @@ Unknown inline-button callbacks are forwarded to π as `[callback] <data>` when
|
|
|
209
209
|
|
|
210
210
|
### Extension Sections
|
|
211
211
|
|
|
212
|
-
Ordinary pi extensions can register structured UI sections that appear in the main Telegram menu and Settings submenu without owning a second polling loop. Each section gets a narrow typed context with `edit`, `open`, `enqueuePrompt`, `answerCallback`, and `callbackData()` — enough to build interactive Telegram-native surfaces while `pi-telegram` owns transport, callback routing, navigation hierarchy, and diagnostics.
|
|
212
|
+
Ordinary pi extensions can register structured UI sections that appear in the main Telegram menu and Settings submenu without owning a second polling loop. Companion extensions can also register compact status lines for the `/start` menu status text, allowing widgets such as quota indicators to appear beside Status, Usage, Cost, and Context only when relevant to the active model. Each section gets a narrow typed context with `edit`, `open`, `enqueuePrompt`, `answerCallback`, and `callbackData()` — enough to build interactive Telegram-native surfaces while `pi-telegram` owns transport, callback routing, navigation hierarchy, and diagnostics.
|
|
213
213
|
|
|
214
214
|
Import `registerTelegramSection()` from `@llblab/pi-telegram/sections` and return a disposer on shutdown. Sections can send interactive messages directly into the chat via `ctx.open()` — confirmation dialogs, approve/deny gates, and multi-step forms live outside the menu hierarchy while callbacks route through the same typed handler. See [`@llblab/pi-telegram-extension-demo`](https://github.com/llblab/pi-telegram-extension-demo) for a working reference and the [Extension Sections Standard](./docs/sections.md) for the full contract.
|
|
215
215
|
|
|
@@ -260,6 +260,12 @@ Modes are `hidden`, `always`, and `interval`. `hidden` means no time line is add
|
|
|
260
260
|
|
|
261
261
|
Third-party extensions that integrate with `pi-telegram`:
|
|
262
262
|
|
|
263
|
+
- [`pi-codex-usage`](https://github.com/llblab/pi-codex-usage) — Compact Codex subscription quota/status widget for the Pi statusline and the inline menu status text opened by `/start`.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
pi install npm:@llblab/pi-codex-usage
|
|
267
|
+
```
|
|
268
|
+
|
|
263
269
|
- [`pi-telegram-tool-status`](https://github.com/Timur00Kh/pi-telegram-tool-status) — Live-updating service messages that list tools used by the agent. It keeps one message per Telegram prompt and edits it in place as tools execute.
|
|
264
270
|
|
|
265
271
|
```bash
|
package/api/status.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public Telegram status API
|
|
3
|
+
* Zones: package boundary, companion extension interop
|
|
4
|
+
* Exposes compact status-menu line registration for companion extensions while keeping status rendering internals package-private
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
registerTelegramStatusLineProvider,
|
|
9
|
+
type TelegramStatusLineProvider,
|
|
10
|
+
type TelegramStatusLineProviderContext,
|
|
11
|
+
type TelegramStatusLineProviderResult,
|
|
12
|
+
} from "../lib/status.ts";
|
package/docs/public-api.md
CHANGED
|
@@ -16,6 +16,7 @@ Preferred public imports:
|
|
|
16
16
|
```ts
|
|
17
17
|
import telegram from "@llblab/pi-telegram";
|
|
18
18
|
import { registerTelegramSection } from "@llblab/pi-telegram/sections";
|
|
19
|
+
import { registerTelegramStatusLineProvider } from "@llblab/pi-telegram/status";
|
|
19
20
|
import { registerTelegramUpdateHandler } from "@llblab/pi-telegram/updates";
|
|
20
21
|
import { registerTelegramInboundHandler } from "@llblab/pi-telegram/inbound";
|
|
21
22
|
import { registerTelegramOutboundHandler } from "@llblab/pi-telegram/outbound";
|
|
@@ -102,6 +103,9 @@ High-level stable APIs:
|
|
|
102
103
|
- `registerTelegramSection()`
|
|
103
104
|
- Identity: required `id`.
|
|
104
105
|
- Purpose: managed menu/settings UI surfaces.
|
|
106
|
+
- `registerTelegramStatusLineProvider()`
|
|
107
|
+
- Identity: required `id`.
|
|
108
|
+
- Purpose: compact companion status rows in the `/start` menu status text.
|
|
105
109
|
- `registerTelegramVoiceTranscriptionProvider()`
|
|
106
110
|
- Identity: required stable `id` for new code.
|
|
107
111
|
- Purpose: STT fallback for voice/audio input.
|
|
@@ -165,6 +169,27 @@ Contract:
|
|
|
165
169
|
|
|
166
170
|
Full behavior: [Extension Sections](./sections.md).
|
|
167
171
|
|
|
172
|
+
## Status Lines
|
|
173
|
+
|
|
174
|
+
Import from `@llblab/pi-telegram/status`.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
const off = registerTelegramStatusLineProvider(
|
|
178
|
+
({ activeModel }) => {
|
|
179
|
+
if (activeModel?.provider !== "example-provider") return undefined;
|
|
180
|
+
return { label: "service", value: "ready" };
|
|
181
|
+
},
|
|
182
|
+
{ id: "@scope/example-status" },
|
|
183
|
+
);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Contract:
|
|
187
|
+
|
|
188
|
+
- Providers are synchronous because `/start` status text is rendered inline with the menu.
|
|
189
|
+
- Return `undefined` when the line is not relevant for the active model.
|
|
190
|
+
- Provider failures are isolated and skipped so optional companion status cannot break the core Telegram menu.
|
|
191
|
+
- The bridge renders rows as `<Label>: <value>` in the same HTML status block as Status, Usage, Cost, and Context, capitalizing the first label character for Telegram UI consistency.
|
|
192
|
+
|
|
168
193
|
## Updates
|
|
169
194
|
|
|
170
195
|
Import from `@llblab/pi-telegram/updates`.
|
package/lib/status.ts
CHANGED
|
@@ -36,9 +36,24 @@ interface TelegramContextUsage {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export interface TelegramStatusActiveModel {
|
|
39
|
+
provider?: string;
|
|
40
|
+
id?: string;
|
|
39
41
|
contextWindow?: number;
|
|
40
42
|
}
|
|
41
43
|
|
|
44
|
+
export interface TelegramStatusLineProviderContext {
|
|
45
|
+
activeModel: TelegramStatusActiveModel | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface TelegramStatusLineProviderResult {
|
|
49
|
+
label: string;
|
|
50
|
+
value: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type TelegramStatusLineProvider = (
|
|
54
|
+
ctx: TelegramStatusLineProviderContext,
|
|
55
|
+
) => TelegramStatusLineProviderResult | undefined;
|
|
56
|
+
|
|
42
57
|
export interface TelegramStatusContext {
|
|
43
58
|
sessionManager: { getEntries(): TelegramStatusSessionEntry[] };
|
|
44
59
|
getContextUsage(): TelegramContextUsage | undefined;
|
|
@@ -52,6 +67,8 @@ export interface TelegramStatusContext {
|
|
|
52
67
|
|
|
53
68
|
export type TelegramRuntimeEventDetailValue = string | number | boolean | null;
|
|
54
69
|
|
|
70
|
+
const TELEGRAM_STATUS_LINE_PROVIDER_REGISTRY_KEY =
|
|
71
|
+
"__piTelegramStatusLineProviders__";
|
|
55
72
|
const MAX_RECENT_TELEGRAM_RUNTIME_EVENTS = 10;
|
|
56
73
|
const MAX_TELEGRAM_RUNTIME_EVENT_MESSAGE_LENGTH = 1000;
|
|
57
74
|
const MAX_TELEGRAM_RUNTIME_EVENT_DETAIL_LENGTH = 1000;
|
|
@@ -166,7 +183,10 @@ export interface TelegramStatusRuntime<
|
|
|
166
183
|
getStatusLines: () => string[];
|
|
167
184
|
}
|
|
168
185
|
|
|
169
|
-
function truncateTelegramRuntimeEventText(
|
|
186
|
+
function truncateTelegramRuntimeEventText(
|
|
187
|
+
text: string,
|
|
188
|
+
maxLength: number,
|
|
189
|
+
): string {
|
|
170
190
|
if (text.length <= maxLength) return text;
|
|
171
191
|
return `${text.slice(0, maxLength).trimEnd()}… [truncated ${text.length - maxLength} chars]`;
|
|
172
192
|
}
|
|
@@ -262,6 +282,61 @@ export function recordTelegramRuntimeEvent(
|
|
|
262
282
|
recordStructuredTelegramRuntimeEvent(events, { category, error }, options);
|
|
263
283
|
}
|
|
264
284
|
|
|
285
|
+
function getOrCreateTelegramStatusLineProviderRegistry(): Map<
|
|
286
|
+
string,
|
|
287
|
+
TelegramStatusLineProvider
|
|
288
|
+
> {
|
|
289
|
+
const existing = (globalThis as Record<string, unknown>)[
|
|
290
|
+
TELEGRAM_STATUS_LINE_PROVIDER_REGISTRY_KEY
|
|
291
|
+
];
|
|
292
|
+
if (existing instanceof Map)
|
|
293
|
+
return existing as Map<string, TelegramStatusLineProvider>;
|
|
294
|
+
const registry = new Map<string, TelegramStatusLineProvider>();
|
|
295
|
+
(globalThis as Record<string, unknown>)[
|
|
296
|
+
TELEGRAM_STATUS_LINE_PROVIDER_REGISTRY_KEY
|
|
297
|
+
] = registry;
|
|
298
|
+
return registry;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Register a compact companion-extension line for the Telegram status menu.
|
|
303
|
+
*
|
|
304
|
+
* Providers are synchronous and should return undefined when their line is not
|
|
305
|
+
* relevant for the active model. Errors are isolated so optional companion
|
|
306
|
+
* status cannot break the core Telegram menu.
|
|
307
|
+
*/
|
|
308
|
+
export function registerTelegramStatusLineProvider(
|
|
309
|
+
provider: TelegramStatusLineProvider,
|
|
310
|
+
options: { id: string },
|
|
311
|
+
): () => void {
|
|
312
|
+
const registry = getOrCreateTelegramStatusLineProviderRegistry();
|
|
313
|
+
registry.set(options.id, provider);
|
|
314
|
+
return () => {
|
|
315
|
+
if (registry.get(options.id) === provider) registry.delete(options.id);
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function getTelegramStatusLineProviderResults(
|
|
320
|
+
ctx: TelegramStatusLineProviderContext,
|
|
321
|
+
): TelegramStatusLineProviderResult[] {
|
|
322
|
+
const results: TelegramStatusLineProviderResult[] = [];
|
|
323
|
+
const registry = getOrCreateTelegramStatusLineProviderRegistry();
|
|
324
|
+
for (const provider of registry.values()) {
|
|
325
|
+
try {
|
|
326
|
+
const result = provider(ctx);
|
|
327
|
+
if (!result?.label || !result.value) continue;
|
|
328
|
+
results.push(result);
|
|
329
|
+
} catch {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return results;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function clearTelegramStatusLineProviders(): void {
|
|
337
|
+
getOrCreateTelegramStatusLineProviderRegistry().clear();
|
|
338
|
+
}
|
|
339
|
+
|
|
265
340
|
export function createTelegramRuntimeEventRecorder(
|
|
266
341
|
options: TelegramRuntimeEventRecorderOptions,
|
|
267
342
|
): TelegramRuntimeEventRecorder {
|
|
@@ -314,7 +389,9 @@ function formatTelegramRuntimeEvent(event: TelegramRuntimeEvent): string {
|
|
|
314
389
|
return `${new Date(event.at).toISOString()} ${formatTelegramRuntimeEventSummary(event)}`;
|
|
315
390
|
}
|
|
316
391
|
|
|
317
|
-
function buildTelegramRuntimeEventSummary(
|
|
392
|
+
function buildTelegramRuntimeEventSummary(
|
|
393
|
+
events: TelegramRuntimeEvent[],
|
|
394
|
+
): string {
|
|
318
395
|
const counts = new Map<string, number>();
|
|
319
396
|
for (const event of events) {
|
|
320
397
|
const category = formatTelegramRuntimeEventCategory(event);
|
|
@@ -553,8 +630,13 @@ function collectUsageStats(ctx: TelegramStatusContext): TelegramUsageStats {
|
|
|
553
630
|
return stats;
|
|
554
631
|
}
|
|
555
632
|
|
|
633
|
+
function formatStatusRowLabel(label: string): string {
|
|
634
|
+
if (!label) return label;
|
|
635
|
+
return `${label[0]?.toUpperCase() ?? ""}${label.slice(1)}`;
|
|
636
|
+
}
|
|
637
|
+
|
|
556
638
|
function buildStatusRow(label: string, value: string): string {
|
|
557
|
-
return `<b>${escapeHtml(label)}:</b> <code>${escapeHtml(value)}</code>`;
|
|
639
|
+
return `<b>${escapeHtml(formatStatusRowLabel(label))}:</b> <code>${escapeHtml(value)}</code>`;
|
|
558
640
|
}
|
|
559
641
|
|
|
560
642
|
function buildUsageSummary(stats: TelegramUsageStats): string | undefined {
|
|
@@ -613,5 +695,8 @@ export function buildStatusHtml(
|
|
|
613
695
|
lines.push(buildStatusRow("Cost", costSummary));
|
|
614
696
|
}
|
|
615
697
|
lines.push(buildStatusRow("Context", buildContextSummary(ctx, activeModel)));
|
|
698
|
+
for (const row of getTelegramStatusLineProviderResults({ activeModel })) {
|
|
699
|
+
lines.push(buildStatusRow(row.label, row.value));
|
|
700
|
+
}
|
|
616
701
|
return lines.join("\n");
|
|
617
702
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-telegram",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"./outbound": "./api/outbound.ts",
|
|
51
51
|
"./updates": "./api/updates.ts",
|
|
52
52
|
"./sections": "./api/sections.ts",
|
|
53
|
+
"./status": "./api/status.ts",
|
|
53
54
|
"./voice": "./api/voice.ts",
|
|
54
55
|
"./keyboard": "./api/keyboard.ts"
|
|
55
56
|
},
|