@llblab/pi-telegram 0.8.1 → 0.9.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/menu.ts CHANGED
@@ -118,6 +118,7 @@ export interface TelegramMenuEffectPort<TModel extends MenuModel = MenuModel> {
118
118
  updateModelMenuMessage: () => Promise<void>;
119
119
  updateThinkingMenuMessage: () => Promise<void>;
120
120
  updateStatusMessage: () => Promise<void>;
121
+ persistScopedModelPatterns?: (patterns: string[]) => Promise<void>;
121
122
  setModel: (model: TModel) => Promise<boolean>;
122
123
  setCurrentModel: (model: TModel) => void;
123
124
  setThinkingLevel: (level: ThinkingLevel) => void;
@@ -188,6 +189,10 @@ export interface TelegramMenuCallbackRuntimeDeps<
188
189
  state: TelegramModelMenuState<TModel>,
189
190
  ctx: TContext,
190
191
  ) => Promise<void>;
192
+ updateSettingsMenuMessage?: (
193
+ state: TelegramModelMenuState<TModel>,
194
+ ctx: TContext,
195
+ ) => Promise<void>;
191
196
  answerCallbackQuery: (
192
197
  callbackQueryId: string,
193
198
  text?: string,
@@ -196,6 +201,10 @@ export interface TelegramMenuCallbackRuntimeDeps<
196
201
  hasActiveTelegramTurn: () => boolean;
197
202
  hasAbortHandler: () => boolean;
198
203
  hasActiveToolExecutions: () => boolean;
204
+ persistScopedModelPatterns?: (
205
+ patterns: string[],
206
+ ctx: TContext,
207
+ ) => Promise<void>;
199
208
  setModel: (model: TModel) => Promise<boolean>;
200
209
  setCurrentModel: (model: TModel, ctx: TContext) => void;
201
210
  stagePendingModelSwitch: (
@@ -265,11 +274,21 @@ export interface TelegramMenuActionRuntime<
265
274
 
266
275
  export type TelegramMenuCallbackAction =
267
276
  | { kind: "ignore" }
268
- | { kind: "status"; action: "model" | "thinking" | "queue" }
277
+ | { kind: "status"; action: "model" | "thinking" | "queue" | "settings" }
269
278
  | { kind: "thinking:set"; level: string }
270
279
  | {
271
280
  kind: "model";
272
- action: "noop" | "scope" | "page" | "pages" | "pick";
281
+ action:
282
+ | "noop"
283
+ | "scope"
284
+ | "page"
285
+ | "pages"
286
+ | "open"
287
+ | "pick"
288
+ | "pick-selected"
289
+ | "scope-enable"
290
+ | "scope-disable"
291
+ | "scope-toggle";
273
292
  value?: string;
274
293
  };
275
294
 
@@ -285,6 +304,9 @@ export function parseTelegramMenuCallbackAction(
285
304
  if (data === "menu:queue" || data === "status:queue") {
286
305
  return { kind: "status", action: "queue" };
287
306
  }
307
+ if (data === "menu:settings" || data === "status:settings") {
308
+ return { kind: "status", action: "settings" };
309
+ }
288
310
  if (data?.startsWith("thinking:set:")) {
289
311
  return {
290
312
  kind: "thinking:set",
@@ -298,7 +320,12 @@ export function parseTelegramMenuCallbackAction(
298
320
  action === "scope" ||
299
321
  action === "page" ||
300
322
  action === "pages" ||
301
- action === "pick"
323
+ action === "open" ||
324
+ action === "pick" ||
325
+ action === "pick-selected" ||
326
+ action === "scope-enable" ||
327
+ action === "scope-disable" ||
328
+ action === "scope-toggle"
302
329
  ) {
303
330
  return { kind: "model", action, value };
304
331
  }
@@ -379,6 +406,10 @@ export interface TelegramMenuCallbackRuntimeAdapterDeps<
379
406
  state: TelegramModelMenuState<TModel>,
380
407
  ctx: TContext,
381
408
  ) => Promise<void>;
409
+ updateSettingsMenuMessage?: (
410
+ state: TelegramModelMenuState<TModel>,
411
+ ctx: TContext,
412
+ ) => Promise<void>;
382
413
  answerCallbackQuery: (
383
414
  callbackQueryId: string,
384
415
  text?: string,
@@ -387,6 +418,10 @@ export interface TelegramMenuCallbackRuntimeAdapterDeps<
387
418
  hasActiveTelegramTurn: () => boolean;
388
419
  hasAbortHandler: () => boolean;
389
420
  getActiveToolExecutions: () => number;
421
+ persistScopedModelPatterns?: (
422
+ patterns: string[],
423
+ ctx: TContext,
424
+ ) => Promise<void>;
390
425
  setModel: (model: TModel) => Promise<boolean>;
391
426
  setCurrentModel: (model: TModel, ctx: TContext) => void;
392
427
  stagePendingModelSwitch: (
@@ -425,11 +460,13 @@ export function createTelegramMenuCallbackHandlerForContext<
425
460
  updateModelMenuMessage: deps.updateModelMenuMessage,
426
461
  updateThinkingMenuMessage: deps.updateThinkingMenuMessage,
427
462
  updateStatusMessage: deps.updateStatusMessage,
463
+ updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
428
464
  answerCallbackQuery: deps.answerCallbackQuery,
429
465
  isIdle: deps.isIdle,
430
466
  hasActiveTelegramTurn: deps.hasActiveTelegramTurn,
431
467
  hasAbortHandler: deps.hasAbortHandler,
432
468
  hasActiveToolExecutions: () => deps.getActiveToolExecutions() > 0,
469
+ persistScopedModelPatterns: deps.persistScopedModelPatterns,
433
470
  setModel: deps.setModel,
434
471
  setCurrentModel: deps.setCurrentModel,
435
472
  stagePendingModelSwitch: deps.stagePendingModelSwitch,
@@ -467,6 +504,8 @@ export async function handleTelegramMenuCallbackRuntime<
467
504
  updateModelMenuMessage: () => deps.updateModelMenuMessage(state, ctx),
468
505
  updateThinkingMenuMessage: () =>
469
506
  deps.updateThinkingMenuMessage(state, ctx),
507
+ updateSettingsMenuMessage: () =>
508
+ deps.updateSettingsMenuMessage?.(state, ctx) ?? Promise.resolve(),
470
509
  answerCallbackQuery: deps.answerCallbackQuery,
471
510
  },
472
511
  ),
@@ -504,6 +543,9 @@ export async function handleTelegramMenuCallbackRuntime<
504
543
  deps.updateModelMenuMessage(state, ctx),
505
544
  updateStatusMessage: () => deps.updateStatusMessage(state, ctx),
506
545
  answerCallbackQuery: deps.answerCallbackQuery,
546
+ persistScopedModelPatterns: deps.persistScopedModelPatterns
547
+ ? (patterns) => deps.persistScopedModelPatterns!(patterns, ctx)
548
+ : undefined,
507
549
  setModel: deps.setModel,
508
550
  setCurrentModel: (model) => deps.setCurrentModel(model, ctx),
509
551
  setThinkingLevel: (level) => {
package/lib/pi.ts CHANGED
@@ -31,7 +31,9 @@ export type {
31
31
 
32
32
  export interface PiSettingsManager {
33
33
  reload: () => Promise<void>;
34
+ flush: () => Promise<void>;
34
35
  getEnabledModels: () => string[] | undefined;
36
+ setEnabledModels: (patterns: string[] | undefined) => void;
35
37
  }
36
38
 
37
39
  export type PiSlashCommandInfo = SlashCommandInfo;
@@ -70,6 +72,20 @@ export function createSettingsManager(cwd: string): PiSettingsManager {
70
72
  return SettingsManager.create(cwd);
71
73
  }
72
74
 
75
+ export function createScopedModelPatternPersister(deps: {
76
+ createSettingsManager: (cwd: string) => PiSettingsManager;
77
+ clearCachedModelMenuInputs: () => void;
78
+ }): (patterns: string[], ctx: ExtensionContext) => Promise<void> {
79
+ return async function persistScopedModelPatterns(patterns, ctx) {
80
+ const settingsManager = deps.createSettingsManager(ctx.cwd);
81
+ settingsManager.setEnabledModels(
82
+ patterns.length > 0 ? patterns : undefined,
83
+ );
84
+ await settingsManager.flush();
85
+ deps.clearCachedModelMenuInputs();
86
+ };
87
+ }
88
+
73
89
  export function getExtensionContextModel(
74
90
  ctx: ExtensionContext,
75
91
  ): ExtensionContext["model"] {
package/lib/preview.ts CHANGED
@@ -89,6 +89,7 @@ export interface TelegramPreviewRuntimeDeps<
89
89
  chunks: TelegramRenderedChunk[],
90
90
  options?: { replyMarkup?: TReplyMarkup },
91
91
  ) => Promise<number | undefined>;
92
+ canSend?: () => boolean;
92
93
  }
93
94
 
94
95
  export interface TelegramPreviewActiveTurn {
@@ -182,6 +183,7 @@ export interface TelegramPreviewControllerDeps<
182
183
  chunks: TelegramRenderedChunk[],
183
184
  options?: { replyMarkup?: TReplyMarkup },
184
185
  ) => Promise<number | undefined>;
186
+ canSend?: () => boolean;
185
187
  throttleMs?: number;
186
188
  maxDraftId?: number;
187
189
  setTimer?: (
@@ -418,6 +420,7 @@ export function createTelegramPreviewController<
418
420
  options,
419
421
  ),
420
422
  editRenderedMessage: deps.editRenderedMessage,
423
+ canSend: deps.canSend,
421
424
  });
422
425
  return {
423
426
  getState: () => state,
@@ -574,6 +577,10 @@ async function performTelegramPreviewFlush<
574
577
  state: TelegramPreviewRuntimeState,
575
578
  deps: TelegramPreviewRuntimeDeps<TReplyMarkup>,
576
579
  ): Promise<void> {
580
+ if (deps.canSend && !deps.canSend()) {
581
+ await clearTelegramPreview(chatId, deps);
582
+ return;
583
+ }
577
584
  const snapshot = buildTelegramPreviewSnapshot({
578
585
  state,
579
586
  maxMessageLength: deps.maxMessageLength,
@@ -658,6 +665,10 @@ export async function finalizeTelegramPreview<
658
665
  ): Promise<boolean> {
659
666
  const state = deps.getState();
660
667
  if (!state) return false;
668
+ if (deps.canSend && !deps.canSend()) {
669
+ await clearTelegramPreview(chatId, deps);
670
+ return false;
671
+ }
661
672
  await flushTelegramPreview(chatId, deps);
662
673
  const finalText = buildTelegramPreviewFinalText(state);
663
674
  if (!finalText) {
@@ -683,6 +694,10 @@ export async function finalizeTelegramMarkdownPreview<
683
694
  ): Promise<boolean> {
684
695
  const state = deps.getState();
685
696
  if (!state) return false;
697
+ if (deps.canSend && !deps.canSend()) {
698
+ await clearTelegramPreview(chatId, deps);
699
+ return false;
700
+ }
686
701
  await flushTelegramPreview(chatId, deps);
687
702
  const chunks = deps.renderTelegramMessage(markdown, { mode: "markdown" });
688
703
  if (chunks.length === 0) {
package/lib/prompts.ts CHANGED
@@ -18,7 +18,9 @@ Inbound context:
18
18
  - Unknown \`[callback] ...\` messages may be intended for another extension; if you see one, say the callback was not handled and the environment may be misconfigured.
19
19
 
20
20
  Telegram-visible output:
21
- - Telegram is often phone-width; prefer narrow table columns because wide monospace tables can become unreadable.
21
+ - Telegram is often phone-width; keep tables, dense list items, and compact text blocks at or below 37 visible cells when possible.
22
+ - Count display width, not raw characters: emoji and some glyphs are wide, so prefer shorter labels when unsure.
23
+ - Wide monospace blocks can become unreadable on mobile; use them only when structure or literal code requires them.
22
24
  - For requested/generated files, call tool \`telegram_attach(local_path)\`; mentioning a local path in text does not send it.
23
25
 
24
26
  Native outbound actions:
@@ -55,3 +57,24 @@ export function createTelegramBeforeAgentStartHook(
55
57
  systemPromptSuffix: options.systemPromptSuffix ?? SYSTEM_PROMPT_SUFFIX,
56
58
  });
57
59
  }
60
+
61
+ export interface TelegramProactivePromptHookDeps<TContext> {
62
+ baseHook?: (event: BeforeAgentStartEvent) => { systemPrompt: string };
63
+ isProactivePushEnabled: () => boolean;
64
+ isCurrentOwner: (ctx: TContext) => boolean;
65
+ }
66
+
67
+ export function createTelegramProactiveBeforeAgentStartHook<TContext>(
68
+ deps: TelegramProactivePromptHookDeps<TContext>,
69
+ ): (
70
+ event: BeforeAgentStartEvent,
71
+ ctx: TContext,
72
+ ) => Promise<{ systemPrompt: string }> {
73
+ const baseHook = deps.baseHook ?? createTelegramBeforeAgentStartHook();
74
+ return async function onBeforeAgentStart(event, ctx) {
75
+ const result = baseHook(event);
76
+ if (!deps.isProactivePushEnabled()) return result;
77
+ if (!deps.isCurrentOwner(ctx)) return result;
78
+ return result;
79
+ };
80
+ }
package/lib/queue.ts CHANGED
@@ -357,7 +357,7 @@ function formatTelegramQueueItemStatusSummary<TContext = unknown>(
357
357
  item: TelegramQueueItem<TContext>,
358
358
  ): string {
359
359
  if (item.queueLane === "priority") {
360
- return `${item.kind === "prompt" ? item.priorityEmoji ?? "⚡" : "⚡"} ${item.statusSummary}`;
360
+ return `${item.kind === "prompt" ? (item.priorityEmoji ?? "⚡") : "⚡"} ${item.statusSummary}`;
361
361
  }
362
362
  return item.statusSummary;
363
363
  }
@@ -365,15 +365,7 @@ function formatTelegramQueueItemStatusSummary<TContext = unknown>(
365
365
  export function formatQueuedTelegramItemsStatus<TContext = unknown>(
366
366
  items: TelegramQueueItem<TContext>[],
367
367
  ): string {
368
- if (items.length === 0) return "";
369
- const previewCount = 4;
370
- const summaries = items
371
- .slice(0, previewCount)
372
- .map(formatTelegramQueueItemStatusSummary)
373
- .filter(Boolean);
374
- if (summaries.length === 0) return ` +${items.length}`;
375
- const suffix = items.length > summaries.length ? ", …" : "";
376
- return ` +${items.length}: [${summaries.join(", ")}${suffix}]`;
368
+ return items.length === 0 ? "" : ` +${items.length}`;
377
369
  }
378
370
 
379
371
  export function canDispatchTelegramTurnState(
@@ -771,6 +763,7 @@ export interface TelegramAgentEndRuntimeDeps<
771
763
  preserveQueuedTurnsAsHistory: boolean;
772
764
  resetRuntimeState: () => void;
773
765
  updateStatus: () => void;
766
+ isCurrentOwner?: () => boolean;
774
767
  dispatchNextQueuedTelegramTurn: () => void;
775
768
  clearPreview: (chatId: number) => Promise<void>;
776
769
  setPreviewPendingText: (text: string) => void;
@@ -782,7 +775,7 @@ export interface TelegramAgentEndRuntimeDeps<
782
775
  ) => Promise<boolean>;
783
776
  sendMarkdownReply: (
784
777
  chatId: number,
785
- replyToMessageId: number,
778
+ replyToMessageId: number | undefined,
786
779
  markdown: string,
787
780
  options?: { replyMarkup?: TReplyMarkup },
788
781
  ) => Promise<unknown>;
@@ -800,6 +793,14 @@ export interface TelegramAgentEndRuntimeDeps<
800
793
  plan: TelegramAgentEndOutboundReplyPlan,
801
794
  options?: { replyToPrompt?: boolean },
802
795
  ) => Promise<void>;
796
+ getDefaultChatId?: () => number | undefined;
797
+ consumeProactiveReplyToMessageId?: (chatId: number) => number | undefined;
798
+ isProactivePushEnabled?: () => boolean;
799
+ recordRuntimeEvent?: (
800
+ category: string,
801
+ error: unknown,
802
+ details?: Record<string, unknown>,
803
+ ) => void;
803
804
  }
804
805
 
805
806
  export interface TelegramAgentEndHookRuntimeDeps<
@@ -815,6 +816,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
815
816
  getPreserveQueuedTurnsAsHistory: () => boolean;
816
817
  resetRuntimeState: () => void;
817
818
  updateStatus: (ctx: TContext) => void;
819
+ isCurrentOwner?: (ctx: TContext) => boolean;
818
820
  dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
819
821
  requestDeferredDispatchNextQueuedTelegramTurn: (
820
822
  dispatch: (ctx: TContext) => void,
@@ -836,6 +838,10 @@ export interface TelegramAgentEndHookRuntimeDeps<
836
838
  TReplyMarkup
837
839
  >["planOutboundReply"];
838
840
  sendOutboundReplyArtifacts?: TelegramAgentEndRuntimeDeps<TTurn>["sendOutboundReplyArtifacts"];
841
+ getDefaultChatId?: TelegramAgentEndRuntimeDeps<TTurn>["getDefaultChatId"];
842
+ consumeProactiveReplyToMessageId?: TelegramAgentEndRuntimeDeps<TTurn>["consumeProactiveReplyToMessageId"];
843
+ isProactivePushEnabled?: TelegramAgentEndRuntimeDeps<TTurn>["isProactivePushEnabled"];
844
+ recordRuntimeEvent?: TelegramAgentEndRuntimeDeps<TTurn>["recordRuntimeEvent"];
839
845
  }
840
846
 
841
847
  export interface TelegramAgentEndHookEvent<TMessage> {
@@ -926,12 +932,17 @@ export function createTelegramAgentEndHook<
926
932
  ctx: TContext,
927
933
  ): Promise<void> {
928
934
  const turn = deps.getActiveTurn();
935
+ const proactiveEnabled = deps.isProactivePushEnabled?.() ?? false;
929
936
  await handleTelegramAgentEndRuntime({
930
937
  turn,
931
- assistant: turn ? deps.extractAssistant(event.messages) : {},
938
+ assistant:
939
+ turn || proactiveEnabled ? deps.extractAssistant(event.messages) : {},
932
940
  preserveQueuedTurnsAsHistory: deps.getPreserveQueuedTurnsAsHistory(),
933
941
  resetRuntimeState: deps.resetRuntimeState,
934
942
  updateStatus: () => deps.updateStatus(ctx),
943
+ isCurrentOwner: deps.isCurrentOwner
944
+ ? () => deps.isCurrentOwner?.(ctx) ?? false
945
+ : undefined,
935
946
  dispatchNextQueuedTelegramTurn: () => {
936
947
  deps.requestDeferredDispatchNextQueuedTelegramTurn(
937
948
  deps.dispatchNextQueuedTelegramTurn,
@@ -945,6 +956,10 @@ export function createTelegramAgentEndHook<
945
956
  sendQueuedAttachments: deps.sendQueuedAttachments,
946
957
  planOutboundReply: deps.planOutboundReply,
947
958
  sendOutboundReplyArtifacts: deps.sendOutboundReplyArtifacts,
959
+ getDefaultChatId: deps.getDefaultChatId,
960
+ consumeProactiveReplyToMessageId: deps.consumeProactiveReplyToMessageId,
961
+ isProactivePushEnabled: deps.isProactivePushEnabled,
962
+ recordRuntimeEvent: deps.recordRuntimeEvent,
948
963
  });
949
964
  };
950
965
  }
@@ -964,6 +979,10 @@ export async function handleTelegramAgentEndRuntime<
964
979
  const replyMarkup = outboundReply?.replyMarkup;
965
980
  deps.resetRuntimeState();
966
981
  deps.updateStatus();
982
+ if (deps.isCurrentOwner && !deps.isCurrentOwner()) {
983
+ if (turn) await deps.clearPreview(turn.chatId);
984
+ return;
985
+ }
967
986
  const endPlan = buildTelegramAgentEndPlan({
968
987
  hasTurn: !!turn,
969
988
  stopReason: assistant.stopReason,
@@ -972,6 +991,28 @@ export async function handleTelegramAgentEndRuntime<
972
991
  preserveQueuedTurnsAsHistory: deps.preserveQueuedTurnsAsHistory,
973
992
  });
974
993
  if (!turn) {
994
+ if (
995
+ deps.isProactivePushEnabled?.() &&
996
+ finalText &&
997
+ !assistant.errorMessage
998
+ ) {
999
+ const defaultChatId = deps.getDefaultChatId?.();
1000
+ if (defaultChatId !== undefined) {
1001
+ const replyToMessageId =
1002
+ deps.consumeProactiveReplyToMessageId?.(defaultChatId);
1003
+ try {
1004
+ await deps.sendMarkdownReply(
1005
+ defaultChatId,
1006
+ replyToMessageId,
1007
+ finalText,
1008
+ );
1009
+ } catch (error) {
1010
+ deps.recordRuntimeEvent?.("proactive-push", error, {
1011
+ chatId: defaultChatId,
1012
+ });
1013
+ }
1014
+ }
1015
+ }
975
1016
  if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
976
1017
  return;
977
1018
  }
package/lib/routing.ts CHANGED
@@ -50,11 +50,24 @@ export interface TelegramInboundRouteRuntimeDeps<
50
50
  Model.ScopedTelegramModel<TModel>
51
51
  >;
52
52
  menuActions: Menu.TelegramMenuActionRuntime<TContext, TModel>;
53
+ updateSettingsMenuMessage?: (
54
+ state: Menu.TelegramModelMenuState<TModel>,
55
+ ctx: TContext,
56
+ ) => Promise<void>;
53
57
  openQueueMenu: (
54
58
  chatId: number,
55
59
  replyToMessageId: number,
56
60
  ctx: TContext,
57
61
  ) => Promise<void>;
62
+ openSettingsMenu?: (
63
+ chatId: number,
64
+ replyToMessageId: number,
65
+ ctx: TContext,
66
+ ) => Promise<void>;
67
+ settingsMenuCallbackHandler?: (
68
+ query: TCallbackQuery,
69
+ ctx: TContext,
70
+ ) => Promise<boolean>;
58
71
  queueMenuCallbackHandler: (
59
72
  query: TCallbackQuery,
60
73
  ctx: TContext,
@@ -79,6 +92,10 @@ export interface TelegramInboundRouteRuntimeDeps<
79
92
  downloadFile: Media.DownloadTelegramMessageFilesDeps["downloadFile"];
80
93
  getThinkingLevel: () => Model.ThinkingLevel;
81
94
  setThinkingLevel: (level: Model.ThinkingLevel) => void;
95
+ persistScopedModelPatterns?: (
96
+ patterns: string[],
97
+ ctx: TContext,
98
+ ) => Promise<void>;
82
99
  setModel: (model: TModel) => Promise<boolean>;
83
100
  sendUserMessage?: (message: string) => void;
84
101
  isIdle: (ctx: TContext) => boolean;
@@ -98,6 +115,7 @@ const TELEGRAM_OWNED_CALLBACK_PREFIXES = [
98
115
  "menu:",
99
116
  "model:",
100
117
  "queue:",
118
+ "settings:",
101
119
  "status:",
102
120
  "tgbtn:",
103
121
  "thinking:",
@@ -140,12 +158,14 @@ export function createTelegramInboundRouteRuntime<
140
158
  updateModelMenuMessage: deps.menuActions.updateModelMenuMessage,
141
159
  updateThinkingMenuMessage: deps.menuActions.updateThinkingMenuMessage,
142
160
  updateStatusMessage: deps.menuActions.updateStatusMessage,
161
+ updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
143
162
  answerCallbackQuery: deps.answerCallbackQuery,
144
163
  isIdle: deps.isIdle,
145
164
  hasActiveTelegramTurn: deps.activeTurnRuntime.has,
146
165
  hasAbortHandler: deps.bridgeRuntime.abort.hasHandler,
147
166
  getActiveToolExecutions:
148
167
  deps.bridgeRuntime.lifecycle.getActiveToolExecutions,
168
+ persistScopedModelPatterns: deps.persistScopedModelPatterns,
149
169
  setModel: deps.setModel,
150
170
  setCurrentModel: deps.currentModelRuntime.setCurrentModel,
151
171
  stagePendingModelSwitch: deps.modelSwitchController.stagePendingSwitch,
@@ -187,6 +207,11 @@ export function createTelegramInboundRouteRuntime<
187
207
  }
188
208
  const handledByQueue = await deps.queueMenuCallbackHandler(query, ctx);
189
209
  if (handledByQueue) return;
210
+ const handledBySettings = await deps.settingsMenuCallbackHandler?.(
211
+ query,
212
+ ctx,
213
+ );
214
+ if (handledBySettings) return;
190
215
  const callbackData = query.data;
191
216
  if (
192
217
  deps.sendUserMessage &&
@@ -279,6 +304,7 @@ export function createTelegramInboundRouteRuntime<
279
304
  const chatId = (message as { chat: { id: number } }).chat.id;
280
305
  return deps.openQueueMenu(chatId, message.message_id, ctx);
281
306
  },
307
+ openSettingsMenu: deps.openSettingsMenu,
282
308
  getAllowedUserId: deps.configStore.getAllowedUserId,
283
309
  setAllowedUserId: deps.configStore.setAllowedUserId,
284
310
  setMyCommands: deps.setMyCommands,
package/lib/status.ts CHANGED
@@ -44,6 +44,7 @@ export interface TelegramStatusContext {
44
44
  getContextUsage(): TelegramContextUsage | undefined;
45
45
  isIdle?: () => boolean;
46
46
  hasPendingMessages?: () => boolean;
47
+ isCompactionInProgress?: () => boolean;
47
48
  modelRegistry: {
48
49
  isUsingOAuth(model: TelegramStatusActiveModel): boolean;
49
50
  };
@@ -303,8 +304,13 @@ export function buildTelegramRuntimeEventLines(
303
304
 
304
305
  export function createTelegramStatusHtmlBuilder<TContext>(deps: {
305
306
  getActiveModel: (ctx: TContext) => TelegramStatusActiveModel | undefined;
307
+ isCompactionInProgress?: () => boolean;
306
308
  }): (ctx: TContext & TelegramStatusContext) => string {
307
- return (ctx) => buildStatusHtml(ctx, deps.getActiveModel(ctx));
309
+ return (ctx) =>
310
+ buildStatusHtml(
311
+ { ...ctx, isCompactionInProgress: deps.isCompactionInProgress },
312
+ deps.getActiveModel(ctx),
313
+ );
308
314
  }
309
315
 
310
316
  export function createTelegramStatusRuntime<
@@ -348,7 +354,11 @@ export function createTelegramBridgeStatusRuntime<
348
354
  paired: !!config.allowedUserId,
349
355
  compactionInProgress,
350
356
  processing:
351
- hasActiveTurn || hasPendingDispatch || queuedItems.length > 0,
357
+ hasActiveTurn ||
358
+ hasPendingDispatch ||
359
+ hasPendingModelSwitch ||
360
+ activeToolExecutions > 0 ||
361
+ queuedItems.length > 0,
352
362
  processingStatus: getTelegramStatusBarProcessingStatus({
353
363
  hasActiveTurn,
354
364
  hasPendingDispatch,
@@ -388,8 +398,9 @@ export function getTelegramStatusBarProcessingStatus(state: {
388
398
  queuedItems: number;
389
399
  }): string | undefined {
390
400
  if (state.hasPendingModelSwitch) return "model";
391
- if (state.activeToolExecutions > 0) return "tool running";
392
- if (state.hasActiveTurn) return "active";
401
+ if (state.hasActiveTurn && state.activeToolExecutions > 0)
402
+ return "tool running";
403
+ if (state.hasActiveTurn || state.activeToolExecutions > 0) return "active";
393
404
  if (state.hasPendingDispatch) return "dispatching";
394
405
  if (state.queuedItems > 0) return "queued";
395
406
  return undefined;
@@ -410,13 +421,15 @@ export function buildTelegramStatusBarText(
410
421
  if (!state.paired)
411
422
  return `${label} ${theme.fg("warning", "awaiting pairing")}`;
412
423
  const queued = state.queuedStatus
413
- ? theme.fg("muted", state.queuedStatus)
424
+ ? theme.fg("success", state.queuedStatus)
414
425
  : "";
415
426
  if (state.compactionInProgress) {
416
427
  return `${label} ${theme.fg("accent", "compacting")}${queued}`;
417
428
  }
418
429
  if (state.processing) {
419
- const processingStatus = state.processingStatus ?? "processing";
430
+ const processingStatus = state.queuedStatus
431
+ ? "active"
432
+ : (state.processingStatus ?? "processing");
420
433
  const processingToken =
421
434
  processingStatus === "active" ? "warning" : "accent";
422
435
  return `${label} ${theme.fg(processingToken, processingStatus)}${queued}`;
@@ -537,6 +550,7 @@ function buildContextSummary(
537
550
  }
538
551
 
539
552
  function buildStatusSummary(ctx: TelegramStatusContext): string {
553
+ if (ctx.isCompactionInProgress?.()) return "compacting";
540
554
  if (ctx.hasPendingMessages?.()) return "pending";
541
555
  if (ctx.isIdle?.() === false) return "active";
542
556
  if (ctx.isIdle?.() === true) return "idle";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "description": "Better Telegram DM bridge extension for π",
6
6
  "type": "module",