@llblab/pi-telegram 0.33.0 → 0.33.1

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,11 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
5
+ ## 0.33.1: Rendered Command Replies And Visible Compaction
6
+
7
+ - `Command Rendering`: Restored the `/next` empty-queue emphasis by preserving its HTML source and forwarding `parseMode: "HTML"` through the command reply adapter, so command helpers select the renderer explicitly instead of leaking HTML or Markdown syntax as plain text.
8
+ - `Compaction Visibility`: Reports `compacting` ahead of generic active or pending status and sends the same start/completion notices for observed automatic compaction as for manually requested compaction, while retaining queue blocking and deferred dispatch.
9
+
5
10
  ## 0.33.0: Matrix Controls And Pinned Filesystem Navigation
6
11
 
7
12
  - `Button Matrix`: Extended `telegram_button` and its plural alias from flat arrays to JSON matrices: top-level objects remain full-width rows, while nested arrays intentionally group one to three peer controls horizontally; empty, oversized, or deeper rows fail closed without changing existing object, attribute, or flat-array behavior.
@@ -352,7 +352,7 @@ The bridge does not mirror arbitrary `ctx.ui.confirm/input/select/custom` prompt
352
352
 
353
353
  ## Diagnostics And Operational Behavior
354
354
 
355
- Status rendering distinguishes connected, active, dispatching, queued, tool-running, model-switching, and compacting states. If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, status remains active instead of degrading to connected.
355
+ Status rendering distinguishes connected, active, dispatching, queued, tool-running, model-switching, and compacting states; the Telegram status menu gives compaction precedence over generic active or pending work. Observed automatic compaction sends the same start and completion notices as the manual command without duplicating notices for command-owned compaction. If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, status remains active instead of degrading to connected.
356
356
 
357
357
  Queue reactions are reversible shortcut controls for waiting turns. The runtime reconciles each complete `MessageReactionUpdated.new_reaction` set: any removal reaction (`👎`, `👻`, `💔`, `💩`, `🗑`) suppresses the governed prompt without discarding its queue authority; otherwise any promotion reaction (`👍`, `⚡️`, `❤️`, `🕊`, `🔥`) moves it to priority; otherwise it returns to the default lane. Suppressed prompts remain visible in the queue menu, survive authenticated queue handoff, and do not block unrelated dispatch. Reaction changes first flush a matching delayed text or media group so the governed turn exists before mutation. Once Pi has consumed a prompt, reactions cannot retract it.
358
358
 
package/lib/bindings.ts CHANGED
@@ -860,6 +860,21 @@ export function registerTelegramLifecycleRuntimeHooks({
860
860
  target: turn?.target,
861
861
  });
862
862
  };
863
+ let observedAutomaticCompaction = false;
864
+ const sendCompactionNotice = async (text: string): Promise<void> => {
865
+ const turn = activeTurnRuntime.get();
866
+ const target = turn?.target ?? proactivePushTargetGetter?.();
867
+ if (!target) return;
868
+ try {
869
+ await sendMarkdownReply(target.chatId, turn?.replyToMessageId, text, {
870
+ target,
871
+ });
872
+ } catch (error) {
873
+ recordRuntimeEvent("delivery", error, {
874
+ phase: "compaction-notice",
875
+ });
876
+ }
877
+ };
863
878
  const compactionObserver = Lifecycle.createTelegramCompactionObserverRuntime({
864
879
  isContextActive: isSessionContextActive,
865
880
  setCompactionInProgress: lifecycle.setCompactionInProgress,
@@ -870,7 +885,10 @@ export function registerTelegramLifecycleRuntimeHooks({
870
885
  deferredQueueDispatchRuntime.request,
871
886
  dispatchNextQueuedTelegramTurn,
872
887
  recordRuntimeEvent,
873
- onCompactionAbandoned: activityRuntime.onCompactionAbandoned,
888
+ onCompactionAbandoned: () => {
889
+ observedAutomaticCompaction = false;
890
+ activityRuntime.onCompactionAbandoned();
891
+ },
874
892
  });
875
893
  const messageActivityTypingHooks =
876
894
  Lifecycle.createTelegramMessageActivityTypingHooks({
@@ -902,6 +920,7 @@ export function registerTelegramLifecycleRuntimeHooks({
902
920
  activityRuntime.onSessionShutdown();
903
921
  activityVerbosityRuntime?.reset();
904
922
  assistantOutputRuntime.stop();
923
+ observedAutomaticCompaction = false;
905
924
  compactionObserver.onSessionShutdown();
906
925
  if (event.reason === "quit" && disconnectOnQuit) {
907
926
  try {
@@ -916,15 +935,24 @@ export function registerTelegramLifecycleRuntimeHooks({
916
935
  }
917
936
  await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
918
937
  },
919
- onSessionBeforeCompact(event, ctx) {
938
+ async onSessionBeforeCompact(event, ctx) {
920
939
  if (!isSessionContextActive(ctx)) return;
940
+ const shouldNotify = !(lifecycle.isCompactionInProgress?.() ?? false);
941
+ if (shouldNotify) observedAutomaticCompaction = true;
921
942
  activityRuntime.onCompactionStart(Pi.getSessionCompactionReason(event));
922
943
  compactionObserver.onSessionBeforeCompact(event, ctx);
944
+ if (shouldNotify) {
945
+ await sendCompactionNotice(Commands.TELEGRAM_COMPACTION_STARTED_TEXT);
946
+ }
923
947
  },
924
- onSessionCompact(event, ctx) {
948
+ async onSessionCompact(event, ctx) {
925
949
  if (!isSessionContextActive(ctx)) return;
926
950
  activityRuntime.onCompactionEnd(Pi.getSessionCompactionReason(event));
927
951
  compactionObserver.onSessionCompact(event, ctx);
952
+ if (observedAutomaticCompaction) {
953
+ observedAutomaticCompaction = false;
954
+ await sendCompactionNotice(Commands.TELEGRAM_COMPACTION_COMPLETED_TEXT);
955
+ }
928
956
  },
929
957
  async onAgentStart(event, ctx) {
930
958
  if (!isSessionContextActive(ctx)) return;
package/lib/commands.ts CHANGED
@@ -185,6 +185,10 @@ export function formatTelegramCommandEmojiPrefix(
185
185
  return `${getTelegramCommandEmoji(command)} `;
186
186
  }
187
187
 
188
+ export const TELEGRAM_COMPACTION_STARTED_TEXT =
189
+ `${formatTelegramCommandEmojiPrefix("compact")}Compaction started.`;
190
+ export const TELEGRAM_COMPACTION_COMPLETED_TEXT = "✅ Compaction completed.";
191
+
188
192
  function formatTelegramBotCommandDescription(
189
193
  command: TelegramCommandEmojiName,
190
194
  description: string,
@@ -716,7 +720,10 @@ export interface TelegramCommandTargetRuntimeDeps<TContext> {
716
720
  chatId: number,
717
721
  replyToMessageId: number,
718
722
  text: string,
719
- options?: { target?: { chatId: number; threadId?: number } },
723
+ options?: {
724
+ parseMode?: "HTML";
725
+ target?: { chatId: number; threadId?: number };
726
+ },
720
727
  ) => Promise<unknown>;
721
728
  }
722
729
 
@@ -734,7 +741,11 @@ export interface TelegramCommandTargetRuntime<
734
741
  showStatus: (message: TMessage, ctx: TContext) => Promise<void>;
735
742
  openModelMenu: (message: TMessage, ctx: TContext) => Promise<void>;
736
743
  openSettingsMenu: (message: TMessage, ctx: TContext) => Promise<void>;
737
- sendTextReply: (message: TMessage, text: string) => Promise<void>;
744
+ sendTextReply: (
745
+ message: TMessage,
746
+ text: string,
747
+ options?: { parseMode?: "HTML" },
748
+ ) => Promise<void>;
738
749
  }
739
750
 
740
751
  export function getTelegramCommandMessageTarget(
@@ -917,9 +928,10 @@ export function createTelegramCommandTargetRuntime<
917
928
  target.threadId,
918
929
  );
919
930
  },
920
- sendTextReply: async (message, text) => {
931
+ sendTextReply: async (message, text, options) => {
921
932
  const target = getTelegramCommandMessageTarget(message);
922
933
  await deps.sendTextReply(target.chatId, target.replyToMessageId, text, {
934
+ ...options,
923
935
  target,
924
936
  });
925
937
  },
@@ -1002,7 +1014,11 @@ export interface TelegramCommandRuntimeDeps<
1002
1014
  registerBotCommands: () => Promise<void>;
1003
1015
  getPromptTemplateCommands?: () => readonly TelegramPromptTemplateMenuCommand[];
1004
1016
  persistConfig: () => Promise<void>;
1005
- sendTextReply: (message: TMessage, text: string) => Promise<void>;
1017
+ sendTextReply: (
1018
+ message: TMessage,
1019
+ text: string,
1020
+ options?: { parseMode?: "HTML" },
1021
+ ) => Promise<void>;
1006
1022
  sendInteractiveMessage?: TelegramCompactConfirmationDeps["sendInteractiveMessage"];
1007
1023
  assertExecutionCurrent?: (message: TMessage) => void;
1008
1024
  }
@@ -1185,11 +1201,14 @@ export async function handleTelegramNextCommand(deps: {
1185
1201
  dispatchNextQueuedTurn: () => void;
1186
1202
  clearFoldForDispatch: () => void;
1187
1203
  updateStatus: () => void;
1188
- sendTextReply: (text: string) => Promise<void>;
1204
+ sendTextReply: (
1205
+ text: string,
1206
+ options?: { parseMode?: "HTML" },
1207
+ ) => Promise<void>;
1189
1208
  }): Promise<void> {
1190
1209
  deps.clearPendingModelSwitch();
1191
1210
  if (!deps.hasQueuedItems()) {
1192
- await deps.sendTextReply("**Queue is empty.**");
1211
+ await deps.sendTextReply("<b>Queue is empty.</b>", { parseMode: "HTML" });
1193
1212
  return;
1194
1213
  }
1195
1214
  if (!deps.isIdle() && deps.hasAbortHandler()) {
@@ -1294,7 +1313,7 @@ export async function handleTelegramCompactConfirmationCallback<TContext>(
1294
1313
  await deps.editInteractiveMessage(
1295
1314
  chatId,
1296
1315
  messageId,
1297
- `${formatTelegramCommandEmojiPrefix("compact")}Compaction started.`,
1316
+ TELEGRAM_COMPACTION_STARTED_TEXT,
1298
1317
  "plain",
1299
1318
  { inline_keyboard: [] },
1300
1319
  );
@@ -1335,7 +1354,7 @@ export async function handleTelegramCompactCommand(
1335
1354
  deps.setCompactionInProgress(false);
1336
1355
  deps.updateStatus();
1337
1356
  dispatchNextQueuedTelegramTurnAfterCompact(deps);
1338
- void deps.sendTextReply("✅ Compaction completed.");
1357
+ void deps.sendTextReply(TELEGRAM_COMPACTION_COMPLETED_TEXT);
1339
1358
  },
1340
1359
  onError: (error) => {
1341
1360
  deps.stopTypingLoop?.();
@@ -1358,7 +1377,7 @@ export async function handleTelegramCompactCommand(
1358
1377
  }
1359
1378
  if (!deps.suppressStartNotice) {
1360
1379
  await deps.sendTextReply(
1361
- `${formatTelegramCommandEmojiPrefix("compact")}Compaction started.`,
1380
+ TELEGRAM_COMPACTION_STARTED_TEXT,
1362
1381
  );
1363
1382
  }
1364
1383
  }
@@ -1631,11 +1650,13 @@ async function handleTelegramCommandRuntime<
1631
1650
  ): Promise<boolean> {
1632
1651
  const assertExecutionCurrentFor = (nextMessage: TMessage) => (): void =>
1633
1652
  deps.assertExecutionCurrent?.(nextMessage);
1634
- const sendReplyFor = (nextMessage: TMessage) => async (text: string) => {
1635
- deps.assertExecutionCurrent?.(nextMessage);
1636
- await deps.sendTextReply(nextMessage, text);
1637
- deps.assertExecutionCurrent?.(nextMessage);
1638
- };
1653
+ const sendReplyFor =
1654
+ (nextMessage: TMessage) =>
1655
+ async (text: string, options?: { parseMode?: "HTML" }) => {
1656
+ deps.assertExecutionCurrent?.(nextMessage);
1657
+ await deps.sendTextReply(nextMessage, text, options);
1658
+ deps.assertExecutionCurrent?.(nextMessage);
1659
+ };
1639
1660
  const updateStatusFor = (commandCtx: TContext) => () =>
1640
1661
  deps.updateStatus(commandCtx);
1641
1662
  return executeTelegramCommandAction(
package/lib/status.ts CHANGED
@@ -1505,6 +1505,7 @@ function buildContextSummary(
1505
1505
  }
1506
1506
 
1507
1507
  function buildStatusSummary(ctx: TelegramStatusContext): string {
1508
+ if (ctx.isCompactionInProgress?.()) return "compacting";
1508
1509
  if (ctx.hasPendingMessages?.()) return "pending";
1509
1510
  if (ctx.isIdle?.() === false) return "active";
1510
1511
  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.33.0",
3
+ "version": "0.33.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"