@llblab/pi-telegram 0.24.10 → 0.25.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/lib/config.ts CHANGED
@@ -55,6 +55,7 @@ export interface ResolvedTelegramTimeConfig {
55
55
  }
56
56
 
57
57
  export type TelegramAssistantRenderingMode = "rich" | "html";
58
+ export type TelegramActivityVerbosity = "quiet" | "verbose";
58
59
 
59
60
  export interface TelegramConfig {
60
61
  /** @deprecated persisted identity belongs in profiles.default; retained for effective/legacy views */
@@ -74,6 +75,9 @@ export interface TelegramConfig {
74
75
  draftPreviews?: boolean;
75
76
  rendering?: TelegramAssistantRenderingMode;
76
77
  proactivePush?: boolean;
78
+ activity?: TelegramActivityVerbosity;
79
+ /** @deprecated use activity */
80
+ activityVerbosity?: TelegramActivityVerbosity;
77
81
  };
78
82
  /** @deprecated use assistant.draftPreviews */
79
83
  draftPreviews?: boolean;
@@ -678,7 +682,7 @@ export function createTelegramProactivePushSetter(
678
682
  return async (enabled) => {
679
683
  await loadLatestTelegramConfig(configStore);
680
684
  const current = configStore.get();
681
- const config = {
685
+ const config: TelegramConfig = {
682
686
  ...current,
683
687
  assistant: { ...current.assistant, proactivePush: enabled },
684
688
  };
@@ -746,6 +750,40 @@ export function createTelegramAssistantRenderingModeSetter(
746
750
  };
747
751
  }
748
752
 
753
+ export function createTelegramActivityVerbosityGetter(
754
+ configStore: Pick<TelegramConfigStore, "get">,
755
+ ): () => TelegramActivityVerbosity {
756
+ return () => {
757
+ const assistant = configStore.get().assistant;
758
+ if (assistant?.activity !== undefined) {
759
+ return assistant.activity === "verbose" ? "verbose" : "quiet";
760
+ }
761
+ return assistant?.activityVerbosity === "verbose" ? "verbose" : "quiet";
762
+ };
763
+ }
764
+
765
+ export function createTelegramActivityVerbositySetter(
766
+ configStore: TelegramMutableConfigStore,
767
+ ): (verbosity: TelegramActivityVerbosity) => Promise<void> {
768
+ return async (verbosity) => {
769
+ await loadLatestTelegramConfig(configStore);
770
+ const current = configStore.get();
771
+ const {
772
+ activityVerbosity: _legacyActivityVerbosity,
773
+ ...assistant
774
+ } = current.assistant ?? {};
775
+ const config = {
776
+ ...current,
777
+ assistant: {
778
+ ...assistant,
779
+ activity: verbosity,
780
+ },
781
+ };
782
+ configStore.set(config);
783
+ await configStore.persist(config);
784
+ };
785
+ }
786
+
749
787
  export function createTelegramVoiceReplyModeGetter(
750
788
  configStore: Pick<TelegramConfigStore, "get">,
751
789
  ): () => "hidden" | "mirror" | "always" {
@@ -919,6 +957,10 @@ export function createTelegramConfigControls(
919
957
  createTelegramAssistantRenderingModeGetter(configStore),
920
958
  setAssistantRenderingMode:
921
959
  createTelegramAssistantRenderingModeSetter(configStore),
960
+ getActivityVerbosity:
961
+ createTelegramActivityVerbosityGetter(configStore),
962
+ setActivityVerbosity:
963
+ createTelegramActivityVerbositySetter(configStore),
922
964
  getVoiceReplyMode: createTelegramVoiceReplyModeGetter(configStore),
923
965
  isVoiceReplyModeConfigured:
924
966
  createTelegramVoiceReplyModeConfiguredChecker(configStore),
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import type {
8
+ TelegramActivityVerbosity,
8
9
  TelegramAssistantRenderingMode,
9
10
  TelegramTimeMode,
10
11
  } from "./config.ts";
@@ -23,6 +24,7 @@ export interface TelegramSettingsStateDeps {
23
24
  isProactivePushEnabled: () => boolean;
24
25
  areDraftPreviewsEnabled: () => boolean;
25
26
  getAssistantRenderingMode: () => TelegramAssistantRenderingMode;
27
+ getActivityVerbosity: () => TelegramActivityVerbosity;
26
28
  getTimeInjectionMode: () => TelegramTimeMode;
27
29
  getVoiceReplyMode: () => TelegramVoiceReplyMode;
28
30
  isVoiceReplyModeConfigured: () => boolean;
@@ -35,6 +37,9 @@ export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps
35
37
  setAssistantRenderingMode: (
36
38
  mode: TelegramAssistantRenderingMode,
37
39
  ) => Promise<void>;
40
+ setActivityVerbosity: (
41
+ verbosity: TelegramActivityVerbosity,
42
+ ) => Promise<void>;
38
43
  setVoiceReplyMode: (
39
44
  mode: TelegramVoiceReplyMode | undefined,
40
45
  ) => Promise<void>;
@@ -138,6 +143,8 @@ export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>📌 Proactive push:</b>";
138
143
  export const DRAFT_PREVIEWS_SETTINGS_TITLE = "<b>📝 Draft previews:</b>";
139
144
  export const ASSISTANT_RENDERING_SETTINGS_TITLE =
140
145
  "<b>🧾 Assistant rendering:</b>";
146
+ export const ACTIVITY_VERBOSITY_SETTINGS_TITLE =
147
+ "<b>🔬 Activity:</b>";
141
148
  export const TIME_INJECTION_MODE_SETTINGS_TITLE =
142
149
  "<b>🕒 Time injection mode:</b>";
143
150
  export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
@@ -213,6 +220,19 @@ export function buildAssistantRenderingSettingsText(
213
220
  ].join("\n");
214
221
  }
215
222
 
223
+ export function buildActivityVerbositySettingsText(
224
+ verbosity: TelegramActivityVerbosity,
225
+ ): string {
226
+ return [
227
+ `${ACTIVITY_VERBOSITY_SETTINGS_TITLE} <code>${verbosity}</code>`,
228
+ "",
229
+ "Choose how much technical model activity Telegram shows.",
230
+ "",
231
+ "<code>-</code> <code>quiet</code> (default): show public assistant output without reasoning or tool traffic.",
232
+ "<code>-</code> <code>verbose</code>: add ephemeral available reasoning and durable collapsed tool details.",
233
+ ].join("\n");
234
+ }
235
+
216
236
  export function buildVoiceReplyModeSettingsText(
217
237
  mode: TelegramVoiceReplyMode,
218
238
  configured = true,
@@ -255,6 +275,7 @@ export function buildTelegramSettingsMenuReplyMarkup(
255
275
  sectionRegistryOrVoiceReplyModeConfigured?: TelegramSectionRegistry | boolean,
256
276
  voiceReplyModeConfigured = true,
257
277
  automaticThreadCleanupEnabled = true,
278
+ activityVerbosity: TelegramActivityVerbosity = "quiet",
258
279
  ): TelegramSettingsMenuReplyMarkup {
259
280
  const hasRenderingMode =
260
281
  assistantRenderingModeOrVoiceReplyMode === "rich" ||
@@ -280,23 +301,17 @@ export function buildTelegramSettingsMenuReplyMarkup(
280
301
  const rows: Array<Array<{ text: string; callback_data: string }>> = [
281
302
  [{ text: "⬆️ Main menu", callback_data: "menu:back" }],
282
303
  ];
283
- // Extension settings rows before built-in controls
284
- if (sectionRegistry) {
285
- const settingsRows = getTelegramExtensionSettingsRows(sectionRegistry);
286
- for (const row of settingsRows) {
287
- rows.push([{ text: row.label, callback_data: row.callback_data }]);
288
- }
289
- }
290
- rows.push(
291
- [
292
- {
293
- text: `🧹 Thread cleanup: ${automaticThreadCleanupEnabled ? "on" : "off"}`,
294
- callback_data: "settings:open:automatic-thread-cleanup",
295
- },
296
- ],
297
- [
298
- {
299
- text: `👄 Voice reply: ${getTelegramSettingsStateValueLabel(
304
+ const settingsButtons: Array<{ text: string; callback_data: string }> = [
305
+ {
306
+ text: `📝 Draft previews: ${draftPreviewsEnabled ? "on" : "off"}`,
307
+ callback_data: "settings:open:draft-previews",
308
+ },
309
+ {
310
+ text: `🧾 Rendering: ${assistantRenderingMode}`,
311
+ callback_data: "settings:open:assistant-rendering",
312
+ },
313
+ {
314
+ text: `👄 Voice reply: ${getTelegramSettingsStateValueLabel(
300
315
  getVoiceReplyModeLabel(
301
316
  getVoiceReplyModeSetting(
302
317
  voiceReplyMode,
@@ -304,34 +319,35 @@ export function buildTelegramSettingsMenuReplyMarkup(
304
319
  ),
305
320
  ),
306
321
  )}`,
307
- callback_data: "settings:open:voice-reply",
308
- },
309
- ],
310
- [
311
- {
312
- text: `🕒 Time injection: ${getTelegramSettingsStateValueLabel(timeInjectionMode)}`,
313
- callback_data: "settings:open:time-injection",
314
- },
315
- ],
316
- [
317
- {
318
- text: `📝 Draft previews: ${draftPreviewsEnabled ? "on" : "off"}`,
319
- callback_data: "settings:open:draft-previews",
320
- },
321
- ],
322
- [
323
- {
324
- text: `🧾 Rendering: ${assistantRenderingMode}`,
325
- callback_data: "settings:open:assistant-rendering",
326
- },
327
- ],
328
- [
329
- {
330
- text: `📌 Proactive push: ${proactivePushEnabled ? "on" : "off"}`,
331
- callback_data: "settings:open:proactive",
332
- },
333
- ],
334
- );
322
+ callback_data: "settings:open:voice-reply",
323
+ },
324
+ {
325
+ text: `🔬 Activity: ${activityVerbosity}`,
326
+ callback_data: "settings:open:activity-verbosity",
327
+ },
328
+ {
329
+ text: `📌 Proactive push: ${proactivePushEnabled ? "on" : "off"}`,
330
+ callback_data: "settings:open:proactive",
331
+ },
332
+ {
333
+ text: `🕒 Time injection: ${getTelegramSettingsStateValueLabel(timeInjectionMode)}`,
334
+ callback_data: "settings:open:time-injection",
335
+ },
336
+ {
337
+ text: `🧹 Thread cleanup: ${automaticThreadCleanupEnabled ? "on" : "off"}`,
338
+ callback_data: "settings:open:automatic-thread-cleanup",
339
+ },
340
+ ];
341
+ if (sectionRegistry) {
342
+ const extensionRows = getTelegramExtensionSettingsRows(sectionRegistry);
343
+ settingsButtons.push(
344
+ ...extensionRows.map((row) => ({
345
+ text: row.label,
346
+ callback_data: row.callback_data,
347
+ })),
348
+ );
349
+ }
350
+ rows.push(...settingsButtons.map((button) => [button]));
335
351
  return { inline_keyboard: rows };
336
352
  }
337
353
 
@@ -354,6 +370,7 @@ export async function openTelegramSettingsMenu<
354
370
  sectionRegistry,
355
371
  deps.isVoiceReplyModeConfigured(),
356
372
  deps.isAutomaticThreadCleanupEnabled(),
373
+ deps.getActivityVerbosity(),
357
374
  ),
358
375
  );
359
376
  if (messageId === undefined) return;
@@ -439,6 +456,23 @@ export function buildAssistantRenderingSettingsReplyMarkup(
439
456
  };
440
457
  }
441
458
 
459
+ export function buildActivityVerbositySettingsReplyMarkup(
460
+ verbosity: TelegramActivityVerbosity,
461
+ ): TelegramSettingsMenuReplyMarkup {
462
+ const values: TelegramActivityVerbosity[] = ["quiet", "verbose"];
463
+ return {
464
+ inline_keyboard: [
465
+ [{ text: "⬆️ Back", callback_data: "settings:list" }],
466
+ ...values.map((value) => [
467
+ {
468
+ text: `${value === verbosity ? "🟢 " : ""}${value}`,
469
+ callback_data: `settings:set:activity-verbosity:${value}`,
470
+ },
471
+ ]),
472
+ ],
473
+ };
474
+ }
475
+
442
476
  export function buildTimeInjectionModeSettingsReplyMarkup(
443
477
  mode: TelegramTimeMode,
444
478
  ): TelegramSettingsMenuReplyMarkup {
@@ -490,6 +524,7 @@ export async function updateTelegramSettingsMenuMessage(
490
524
  sectionRegistry,
491
525
  deps.isVoiceReplyModeConfigured(),
492
526
  deps.isAutomaticThreadCleanupEnabled(),
527
+ deps.getActivityVerbosity(),
493
528
  ),
494
529
  );
495
530
  }
@@ -534,6 +569,16 @@ export async function updateAssistantRenderingSettingsMessage(
534
569
  );
535
570
  }
536
571
 
572
+ export async function updateActivityVerbositySettingsMessage(
573
+ deps: TelegramSettingsMenuCallbackDeps,
574
+ ): Promise<void> {
575
+ const verbosity = deps.getActivityVerbosity();
576
+ await deps.updateSettingsMessage(
577
+ buildActivityVerbositySettingsText(verbosity),
578
+ buildActivityVerbositySettingsReplyMarkup(verbosity),
579
+ );
580
+ }
581
+
537
582
  export async function updateTimeInjectionModeSettingsMessage(
538
583
  deps: TelegramSettingsMenuCallbackDeps,
539
584
  ): Promise<void> {
@@ -589,6 +634,11 @@ export async function handleTelegramSettingsMenuCallbackAction(
589
634
  await deps.answerCallbackQuery(callbackQueryId);
590
635
  return true;
591
636
  }
637
+ if (data === "settings:open:activity-verbosity") {
638
+ await updateActivityVerbositySettingsMessage(deps);
639
+ await deps.answerCallbackQuery(callbackQueryId);
640
+ return true;
641
+ }
592
642
  if (data === "settings:open:voice-reply") {
593
643
  await updateVoiceReplyModeSettingsMessage(deps);
594
644
  await deps.answerCallbackQuery(callbackQueryId);
@@ -660,6 +710,18 @@ export async function handleTelegramSettingsMenuCallbackAction(
660
710
  return true;
661
711
  }
662
712
  }
713
+ if (data.startsWith("settings:set:activity-verbosity:")) {
714
+ const verbosity = data.slice("settings:set:activity-verbosity:".length);
715
+ if (verbosity === "quiet" || verbosity === "verbose") {
716
+ await deps.setActivityVerbosity(verbosity);
717
+ await updateActivityVerbositySettingsMessage(deps);
718
+ await deps.answerCallbackQuery(
719
+ callbackQueryId,
720
+ `Activity: ${verbosity}`,
721
+ );
722
+ return true;
723
+ }
724
+ }
663
725
  if (
664
726
  data === "settings:set:automatic-thread-cleanup:on" ||
665
727
  data === "settings:set:automatic-thread-cleanup:off"
@@ -706,6 +768,7 @@ export function createTelegramSettingsMenuRuntime<
706
768
  isProactivePushEnabled: deps.isProactivePushEnabled,
707
769
  areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
708
770
  getAssistantRenderingMode: deps.getAssistantRenderingMode,
771
+ getActivityVerbosity: deps.getActivityVerbosity,
709
772
  getVoiceReplyMode: deps.getVoiceReplyMode,
710
773
  isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
711
774
  getTimeInjectionMode: deps.getTimeInjectionMode,
@@ -729,6 +792,7 @@ export function createTelegramSettingsMenuRuntime<
729
792
  isProactivePushEnabled: deps.isProactivePushEnabled,
730
793
  areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
731
794
  getAssistantRenderingMode: deps.getAssistantRenderingMode,
795
+ getActivityVerbosity: deps.getActivityVerbosity,
732
796
  getVoiceReplyMode: deps.getVoiceReplyMode,
733
797
  isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
734
798
  getTimeInjectionMode: deps.getTimeInjectionMode,
@@ -772,6 +836,7 @@ export function createTelegramSettingsMenuRuntime<
772
836
  isProactivePushEnabled: deps.isProactivePushEnabled,
773
837
  areDraftPreviewsEnabled: deps.areDraftPreviewsEnabled,
774
838
  getAssistantRenderingMode: deps.getAssistantRenderingMode,
839
+ getActivityVerbosity: deps.getActivityVerbosity,
775
840
  getVoiceReplyMode: deps.getVoiceReplyMode,
776
841
  isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
777
842
  getTimeInjectionMode: deps.getTimeInjectionMode,
@@ -779,6 +844,7 @@ export function createTelegramSettingsMenuRuntime<
779
844
  setProactivePushEnabled: deps.setProactivePushEnabled,
780
845
  setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled,
781
846
  setAssistantRenderingMode: deps.setAssistantRenderingMode,
847
+ setActivityVerbosity: deps.setActivityVerbosity,
782
848
  setVoiceReplyMode: deps.setVoiceReplyMode,
783
849
  setTimeInjectionMode: deps.setTimeInjectionMode,
784
850
  setAutomaticThreadCleanupEnabled: deps.setAutomaticThreadCleanupEnabled,
@@ -11,7 +11,7 @@ import type {
11
11
  TelegramInlineKeyboardMarkup,
12
12
  } from "./keyboard.ts";
13
13
  import {
14
- parseTelegramCommentAttributes,
14
+ parseTelegramActionPayload,
15
15
  parseTopLevelTelegramComment,
16
16
  replaceTopLevelHtmlComments,
17
17
  } from "./outbound-markup.ts";
@@ -87,16 +87,27 @@ function normalizeMarkdownAfterButtonExtraction(markdown: string): string {
87
87
  return markdown.replace(/\n{3,}/g, "\n\n").trim();
88
88
  }
89
89
 
90
- function parseButtonsCommentAttributes(input: string): {
91
- label?: string;
92
- prompt?: string;
93
- selectedStyle?: TelegramInlineKeyboardButtonStyle;
94
- } {
95
- const attributes = parseTelegramCommentAttributes(input);
96
- const selectedStyle = attributes.selected_style;
90
+ function getTelegramButtonString(
91
+ payload: Record<string, unknown>,
92
+ key: string,
93
+ ): string | undefined {
94
+ const value = payload[key];
95
+ if (typeof value !== "string") return undefined;
96
+ const trimmed = value.trim();
97
+ return trimmed || undefined;
98
+ }
99
+
100
+ function parseTelegramButtonAction(
101
+ payload: Record<string, unknown>,
102
+ ): TelegramOutboundButtonAction | undefined {
103
+ const value = getTelegramButtonString(payload, "value");
104
+ const label = getTelegramButtonString(payload, "label") ?? value;
105
+ const prompt = getTelegramButtonString(payload, "prompt") ?? value;
106
+ if (!label || !prompt) return undefined;
107
+ const selectedStyle = payload.selected_style;
97
108
  return {
98
- ...(attributes.label ? { label: attributes.label } : {}),
99
- ...(attributes.prompt ? { prompt: attributes.prompt } : {}),
109
+ text: label,
110
+ prompt,
100
111
  ...(selectedStyle === "success" ||
101
112
  selectedStyle === "danger" ||
102
113
  selectedStyle === "primary"
@@ -105,49 +116,6 @@ function parseButtonsCommentAttributes(input: string): {
105
116
  };
106
117
  }
107
118
 
108
- function parseButtonsCommentRows(
109
- head: string,
110
- body: string | undefined,
111
- ): TelegramOutboundButtonAction[][] {
112
- const trimmedHead = head.trim();
113
-
114
- if (body === undefined) {
115
- if (trimmedHead.startsWith(":")) {
116
- const label = trimmedHead.slice(1).trim();
117
- return label ? [[{ text: label, prompt: label }]] : [];
118
- }
119
- const attributes = parseButtonsCommentAttributes(head);
120
- return attributes.label && attributes.prompt
121
- ? [
122
- [
123
- {
124
- text: attributes.label,
125
- prompt: attributes.prompt,
126
- ...(attributes.selectedStyle
127
- ? { selectedStyle: attributes.selectedStyle }
128
- : {}),
129
- },
130
- ],
131
- ]
132
- : [];
133
- }
134
-
135
- const attributes = parseButtonsCommentAttributes(head);
136
- const prompt = body.trim();
137
- if (!attributes.label || !prompt) return [];
138
- return [
139
- [
140
- {
141
- text: attributes.label,
142
- prompt,
143
- ...(attributes.selectedStyle
144
- ? { selectedStyle: attributes.selectedStyle }
145
- : {}),
146
- },
147
- ],
148
- ];
149
- }
150
-
151
119
  export function createTelegramButtonActionStore(
152
120
  options: { ttlMs?: number } = {},
153
121
  ): TelegramButtonActionStore {
@@ -197,14 +165,15 @@ export function planTelegramButtonReply(
197
165
  const stripped = replaceTopLevelHtmlComments(markdown, (comment) => {
198
166
  const command = parseTopLevelTelegramComment(comment, "telegram_button");
199
167
  if (!command) return comment.raw;
200
- const rows = parseButtonsCommentRows(command.head, command.body);
201
- for (const row of rows) {
202
- keyboard.push(
203
- row.map((button) => ({
204
- text: button.text,
205
- callback_data: deps.registerAction(button),
206
- })),
207
- );
168
+ const payload = parseTelegramActionPayload(comment, "telegram_button");
169
+ const action = payload ? parseTelegramButtonAction(payload) : undefined;
170
+ if (action) {
171
+ keyboard.push([
172
+ {
173
+ text: action.text,
174
+ callback_data: deps.registerAction(action),
175
+ },
176
+ ]);
208
177
  }
209
178
  return "";
210
179
  });