@llblab/pi-telegram 0.10.4 โ†’ 0.10.6

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
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.6: Native Typing Keepalive Hotfix
4
+
5
+ - `[Typing]` Telegram native `typing` chat actions now refresh every 2.5s instead of every 4s. Impact: the bot's Telegram-side typing animation has more headroom to stay visible during model retries, transient model/API errors, and other long-running agent work.
6
+ - `[Queue Menu]` Empty queue refresh now rotates through a wider set of small status phrases. Impact: repeatedly refreshing an empty queue feels less repetitive while preserving the same callbacks and menu layout.
7
+ - `[Tests]` Added coverage for the default native typing keepalive cadence.
8
+
9
+ ## 0.10.5: Queue Continuity And Input Resilience Hotfix
10
+
11
+ - `[Compaction]` `/compact` completion and failure callbacks now request deferred queue dispatch instead of dispatching immediately. Impact: queued Telegram turns resume after compaction state and ฯ€ idle/pending-message state have a chance to settle.
12
+ - `[Text Groups]` Long-text split recovery is more aggressive where Telegram chunking actually drifts: the debounce is rounded to 1s, the conservative 3600-character start threshold is preserved, and continuation messages can span a much wider message-id gap while staying scoped to the same chat/user and non-command text. Impact: very large pasted prompts are more likely to arrive as one agent turn instead of several fragmented turns.
13
+ - `[Runtime Status]` Typing-loop and prompt-dispatch status updates are now best-effort and record stale-context failures as structured runtime events. Impact: status/Running indicators remain resilient after error paths without hiding diagnostics.
14
+ - `[Tests]` Added regressions for deferred compact dispatch, stale status failures in typing/dispatch paths, and many-part split-text grouping.
15
+
3
16
  ## 0.10.4: Polling Status Resilience Hotfix
4
17
 
5
18
  - `[Polling]` Status-bar updates from the polling loop are now best-effort and no longer crash the extension when a captured session context becomes stale after session reload. Failures are recorded as structured polling runtime events with `phase: "status-update"`. Impact: polling cleanup and retry status updates stay resilient without changing the Telegram API, config, or operator workflow.
package/index.ts CHANGED
@@ -352,6 +352,8 @@ export default function (pi: Pi.ExtensionAPI) {
352
352
  inboundHandlerRuntime,
353
353
  updateStatus,
354
354
  dispatchNextQueuedTelegramTurn,
355
+ requestDeferredDispatchNextQueuedTelegramTurn:
356
+ deferredQueueDispatchRuntime.request,
355
357
  answerCallbackQuery,
356
358
  editInteractiveMessage,
357
359
  sendInteractiveMessage,
package/lib/commands.ts CHANGED
@@ -317,6 +317,9 @@ export interface TelegramCompactCommandDeps extends TelegramRuntimeEventRecorder
317
317
  setCompactionInProgress: (inProgress: boolean) => void;
318
318
  updateStatus: () => void;
319
319
  dispatchNextQueuedTelegramTurn: () => void;
320
+ requestDeferredDispatchNextQueuedTelegramTurn?: (
321
+ dispatch: () => void,
322
+ ) => void;
320
323
  compact: (callbacks: {
321
324
  onComplete: () => void;
322
325
  onError: (error: unknown) => void;
@@ -547,6 +550,9 @@ export interface TelegramCommandRuntimeDeps<
547
550
  setCompactionInProgress: (inProgress: boolean) => void;
548
551
  updateStatus: (ctx: TContext) => void;
549
552
  dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
553
+ requestDeferredDispatchNextQueuedTelegramTurn?: (
554
+ dispatch: (ctx: TContext) => void,
555
+ ) => void;
550
556
  enqueueContinueTurn: (message: TMessage, ctx: TContext) => Promise<void>;
551
557
  compact: (
552
558
  ctx: TContext,
@@ -758,6 +764,22 @@ export async function handleTelegramContinueCommand<TMessage, TContext>(
758
764
  await deps.enqueueContinueTurn(message, ctx);
759
765
  }
760
766
 
767
+ function dispatchNextQueuedTelegramTurnAfterCompact(
768
+ deps: Pick<
769
+ TelegramCompactCommandDeps,
770
+ | "dispatchNextQueuedTelegramTurn"
771
+ | "requestDeferredDispatchNextQueuedTelegramTurn"
772
+ >,
773
+ ): void {
774
+ if (deps.requestDeferredDispatchNextQueuedTelegramTurn) {
775
+ deps.requestDeferredDispatchNextQueuedTelegramTurn(
776
+ deps.dispatchNextQueuedTelegramTurn,
777
+ );
778
+ return;
779
+ }
780
+ deps.dispatchNextQueuedTelegramTurn();
781
+ }
782
+
761
783
  export async function handleTelegramCompactCommand(
762
784
  deps: TelegramCompactCommandDeps,
763
785
  ): Promise<void> {
@@ -781,13 +803,13 @@ export async function handleTelegramCompactCommand(
781
803
  onComplete: () => {
782
804
  deps.setCompactionInProgress(false);
783
805
  deps.updateStatus();
784
- deps.dispatchNextQueuedTelegramTurn();
806
+ dispatchNextQueuedTelegramTurnAfterCompact(deps);
785
807
  void deps.sendTextReply("Compaction completed.");
786
808
  },
787
809
  onError: (error) => {
788
810
  deps.setCompactionInProgress(false);
789
811
  deps.updateStatus();
790
- deps.dispatchNextQueuedTelegramTurn();
812
+ dispatchNextQueuedTelegramTurnAfterCompact(deps);
791
813
  deps.recordRuntimeEvent?.("compact", error);
792
814
  const errorMessage = getTelegramCommandErrorMessage(error);
793
815
  void deps.sendTextReply(`Compaction failed: ${errorMessage}`);
@@ -1069,6 +1091,13 @@ async function handleTelegramCommandRuntime<
1069
1091
  updateStatus: updateStatusFor(commandCtx),
1070
1092
  dispatchNextQueuedTelegramTurn: () =>
1071
1093
  deps.dispatchNextQueuedTelegramTurn(commandCtx),
1094
+ requestDeferredDispatchNextQueuedTelegramTurn:
1095
+ deps.requestDeferredDispatchNextQueuedTelegramTurn
1096
+ ? (dispatch) =>
1097
+ deps.requestDeferredDispatchNextQueuedTelegramTurn?.(() =>
1098
+ dispatch(),
1099
+ )
1100
+ : undefined,
1072
1101
  compact: (callbacks) => deps.compact(commandCtx, callbacks),
1073
1102
  sendTextReply: sendReplyFor(nextMessage),
1074
1103
  recordRuntimeEvent: deps.recordRuntimeEvent,
package/lib/menu-queue.ts CHANGED
@@ -18,6 +18,14 @@ const EMPTY_QUEUE_REFRESH_TITLES = [
18
18
  "<b>๐Ÿซ™ Still nothing in queue.</b>",
19
19
  "<b>๐Ÿƒ Queue remains empty.</b>",
20
20
  "<b>๐Ÿ•ณ Nothing queued yet.</b>",
21
+ "<b>๐Ÿฆ— Queue crickets continue.</b>",
22
+ "<b>๐ŸŒ™ Queue is peacefully idle.</b>",
23
+ "<b>๐Ÿง˜ Nothing waiting. Very zen.</b>",
24
+ "<b>๐Ÿช Queue orbit is clear.</b>",
25
+ "<b>๐Ÿงบ Basket is empty.</b>",
26
+ "<b>๐Ÿ”ญ No prompts on the horizon.</b>",
27
+ "<b>๐Ÿซง Queue bubbles: none.</b>",
28
+ "<b>๐Ÿ›ธ No queued signals detected.</b>",
21
29
  ] as const;
22
30
  type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
23
31
  interface TelegramQueueMenuItem {
package/lib/routing.ts CHANGED
@@ -79,6 +79,9 @@ export interface TelegramInboundRouteRuntimeDeps<
79
79
  inboundHandlerRuntime: TelegramInboundHandlerRuntime<TContext>;
80
80
  updateStatus: (ctx: TContext, error?: string) => void;
81
81
  dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
82
+ requestDeferredDispatchNextQueuedTelegramTurn?: (
83
+ dispatch: (ctx: TContext) => void,
84
+ ) => void;
82
85
  answerCallbackQuery: (
83
86
  callbackQueryId: string,
84
87
  text?: string,
@@ -339,6 +342,8 @@ export function createTelegramInboundRouteRuntime<
339
342
  deps.bridgeRuntime.lifecycle.setCompactionInProgress,
340
343
  updateStatus: deps.updateStatus,
341
344
  dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
345
+ requestDeferredDispatchNextQueuedTelegramTurn:
346
+ deps.requestDeferredDispatchNextQueuedTelegramTurn,
342
347
  enqueueContinueTurn,
343
348
  compact: deps.compact,
344
349
  allocateItemOrder: deps.bridgeRuntime.queue.allocateItemOrder,
package/lib/runtime.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Owns small session-local runtime primitives that are shared by orchestration but are not specific to queueing, rendering, polling, or Telegram transport
5
5
  */
6
6
 
7
- const TELEGRAM_TYPING_ACTION_INTERVAL_MS = 4000;
7
+ const TELEGRAM_TYPING_ACTION_INTERVAL_MS = 2500;
8
8
 
9
9
  export interface TelegramRuntimeQueueCounters {
10
10
  nextQueuedTelegramItemOrder: number;
@@ -328,6 +328,25 @@ export interface TelegramRuntimeEventRecorderPort {
328
328
  ) => void;
329
329
  }
330
330
 
331
+ function updateTelegramRuntimeStatusSafely<TContext>(
332
+ updateStatus: (ctx: TContext, error?: string) => void,
333
+ ctx: TContext,
334
+ options: {
335
+ error?: string;
336
+ category: string;
337
+ phase: string;
338
+ recordRuntimeEvent?: TelegramRuntimeEventRecorderPort["recordRuntimeEvent"];
339
+ },
340
+ ): void {
341
+ try {
342
+ updateStatus(ctx, options.error);
343
+ } catch (statusError) {
344
+ options.recordRuntimeEvent?.(options.category, statusError, {
345
+ phase: options.phase,
346
+ });
347
+ }
348
+ }
349
+
331
350
  export interface TelegramTypingLoopStarterDeps<
332
351
  TContext,
333
352
  > extends TelegramRuntimeEventRecorderPort {
@@ -351,7 +370,12 @@ export function createTelegramTypingLoopStarter<TContext>(
351
370
  } catch (error) {
352
371
  const message =
353
372
  error instanceof Error ? error.message : String(error);
354
- deps.updateStatus(ctx, message);
373
+ updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
374
+ error: message,
375
+ category: "typing",
376
+ phase: "status-update",
377
+ recordRuntimeEvent: deps.recordRuntimeEvent,
378
+ });
355
379
  deps.recordRuntimeEvent?.("typing", error, {
356
380
  chatId: targetChatId,
357
381
  });
@@ -468,13 +492,22 @@ export function createTelegramPromptDispatchLifecycle<TContext>(
468
492
  onPromptDispatchStart: (ctx: TContext, chatId?: number): void => {
469
493
  deps.lifecycle.setDispatchPending(true);
470
494
  deps.startTypingLoop(ctx, chatId);
471
- deps.updateStatus(ctx);
495
+ updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
496
+ category: "dispatch",
497
+ phase: "status-update",
498
+ recordRuntimeEvent: deps.recordRuntimeEvent,
499
+ });
472
500
  },
473
501
  onPromptDispatchFailure: (ctx: TContext, message: string): void => {
474
502
  deps.lifecycle.clearDispatchPending();
475
503
  deps.typing.stop();
476
504
  deps.recordRuntimeEvent?.("dispatch", new Error(message));
477
- deps.updateStatus(ctx, `dispatch failed: ${message}`);
505
+ updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
506
+ error: `dispatch failed: ${message}`,
507
+ category: "dispatch",
508
+ phase: "status-update",
509
+ recordRuntimeEvent: deps.recordRuntimeEvent,
510
+ });
478
511
  },
479
512
  };
480
513
  }
@@ -4,8 +4,9 @@
4
4
  * Owns conservative delayed grouping for Telegram text messages that look like automatic long-message splits
5
5
  */
6
6
 
7
- const TELEGRAM_TEXT_GROUP_DEBOUNCE_MS = 700;
7
+ const TELEGRAM_TEXT_GROUP_DEBOUNCE_MS = 1000;
8
8
  const TELEGRAM_TEXT_GROUP_MIN_SPLIT_LENGTH = 3600;
9
+ const TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP = 10;
9
10
 
10
11
  export interface TelegramTextGroupMessage {
11
12
  message_id: number;
@@ -91,7 +92,7 @@ function canAppendTelegramTextGroupMessage<
91
92
  return (
92
93
  !!previous &&
93
94
  message.message_id > previous.message_id &&
94
- message.message_id <= previous.message_id + 2 &&
95
+ message.message_id <= previous.message_id + TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP &&
95
96
  text.length > 0 &&
96
97
  !isTelegramTextGroupCommand(text)
97
98
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.10.4",
3
+ "version": "0.10.6",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"