@llblab/pi-telegram 0.16.1 → 0.16.3

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,8 +1,16 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.16.3: Ownership And Shutdown Hotfix
4
4
 
5
- No open changes.
5
+ - `[Ownership]` Lock-gated proactive local/headless final-result push so only the current `/telegram-connect` owner can send non-Telegram agent-end replies to the paired chat, while accepted Telegram turns and queued work still finalize session-locally after polling ownership moves away. Impact: child/headless/non-owner instances no longer leak unrelated local results into Telegram.
6
+ - `[Shutdown]` Made polling retry sleep abort-aware, `unref()`ed non-critical Telegram housekeeping timers, and routed registered session shutdown through the composed lifecycle runtime so session context cleanup runs with queue and polling cleanup. Impact: shutdown and print/headless runs are less likely to stay alive on retry/debounce/typing/preview timers, and direct-delivery ownership context is cleared at the session boundary.
7
+ - `[Tests]` Added regressions for inherited child lock refusal, child-process no-poll ownership, child-process direct-tool non-owner refusal, proactive owner/non-owner/stale/off states, queued dispatch after lock movement, abort-during-retry polling sleep, process-level session shutdown, and real `pi -p` exit/no-leak paths with a local custom provider. Impact: the cross-instance ownership, queue, and shutdown contracts are pinned from unit coverage through CLI smoke coverage.
8
+ - `[Status]` Changed `/telegram-status` bot identity fallback from `not configured` to `unknown` when a bot token exists but the bot username is absent. Impact: live sessions that are paired and polling no longer look unconfigured only because identity metadata is missing.
9
+ - `[Docs]` Documented ownership, proactive-push, timer/shutdown, and multi-extension prompt-boundary contracts in README and `/docs`. Impact: companion extensions get clearer boundaries without adding a core question/prompt-mirroring API.
10
+
11
+ ## 0.16.2: Screenshot Refresh Hotfix
12
+
13
+ - `[Docs]` Refreshed the package screenshot. Impact: npm and repository previews show the current Telegram bridge UI without changing runtime behavior.
6
14
 
7
15
  ## 0.16.1: Disconnected Queue Status Hotfix
8
16
 
package/README.md CHANGED
@@ -47,7 +47,7 @@ Paste your bot token when prompted. If a bot token is already saved in `~/.pi/ag
47
47
  /telegram-connect
48
48
  ```
49
49
 
50
- The adapter is session-local: only one π instance polls Telegram at a time. `/telegram-connect` records only external control/polling ownership in `~/.pi/agent/locks.json`; live ownership moves require confirmation, while `/new` and same-`cwd` restarts resume automatically. Local queue and reply state stay per Pi instance, so an instance that loses Telegram control still finishes work it already accepted.
50
+ The adapter is session-local: only one π instance polls Telegram at a time. `/telegram-connect` records only external control/polling ownership in `~/.pi/agent/locks.json`; live ownership moves require confirmation, inherited child sessions do not start polling unless they take ownership, and `/new` plus same-`cwd` restarts resume automatically. Local queue and reply state stay per Pi instance, so an instance that loses Telegram control still finishes work it already accepted.
51
51
 
52
52
  ### 4. Pair your Telegram account
53
53
 
@@ -215,7 +215,7 @@ Import `registerTelegramSection()` from `@llblab/pi-telegram/sections` and retur
215
215
 
216
216
  ### Proactive push
217
217
 
218
- `telegram.json` can set `proactivePush: true` to send successful local non-Telegram final replies to the paired Telegram chat when no Telegram turn is active. Local prompt text is not mirrored because the bot does not own terminal user messages. The mode is off by default and can be toggled from settings.
218
+ `telegram.json` can set `proactivePush: true` to send successful local non-Telegram final replies to the paired Telegram chat when no Telegram turn is active and this π instance currently owns `/telegram-connect`. Non-owners skip proactive delivery instead of pushing unrelated local/headless results through a bot they no longer control. Local prompt text is not mirrored because the bot does not own terminal user messages. The mode is off by default and can be toggled from settings.
219
219
 
220
220
  ### Time context
221
221
 
@@ -88,10 +88,12 @@ Telegram configuration lives in `~/.pi/agent/telegram.json`. Polling ownership l
88
88
  - `/telegram-connect` acquires or moves singleton polling ownership before polling starts.
89
89
  - `/telegram-disconnect` stops polling and releases ownership.
90
90
  - Session start resumes polling only when the existing lock already points at the current `pid`/`cwd`, or when a stale same-`cwd` lock can be safely replaced after process restart.
91
+ - Inherited child sessions that see the same `telegram.json` but do not own the `pid`/`cwd` lock must not auto-start polling or call `getUpdates` unless the operator force-takes ownership.
91
92
  - Session replacement suspends polling/watchers without releasing ownership so the next session-start hook in the same process can resume.
92
93
  - Live polling owners require explicit takeover confirmation.
93
94
  - Long-lived polling timers use snapshotted ownership context and stop local polling when the lock no longer points at their own process.
94
95
  - `locks.json` owns only external Telegram control/polling. Local extension and queue state are per Pi instance: losing the lock stops live Telegram control here, but does not drain or silence this instance's accepted queue, previews, final delivery, or dispatch.
96
+ - Proactive local/headless final-result push is not accepted-turn delivery. It is allowed only when proactive push is enabled and this instance currently owns the Telegram lock.
95
97
 
96
98
  Deleting `locks.json` resets runtime ownership without deleting Telegram configuration.
97
99
 
@@ -159,7 +161,7 @@ Queued controls:
159
161
  - Prompt-template commands expand Telegram-safe π template aliases before entering the prompt queue.
160
162
  - Model-switch continuation uses the control lane when an in-flight Telegram-owned run must be stopped and resumed.
161
163
 
162
- UI label, navigation, tab, toggle, card, and dialog rules are defined in [UI Style](./ui-style.md). Callback prefix ownership is defined in [Callback Namespaces](./callback-namespaces.md).
164
+ Queue and menu mutations are reachable through Telegram updates handled by the current polling owner. After ownership moves, the old instance keeps processing its accepted local queue, but it no longer receives new menu callbacks or control updates for remote mutation. UI label, navigation, tab, toggle, card, and dialog rules are defined in [UI Style](./ui-style.md). Callback prefix ownership is defined in [Callback Namespaces](./callback-namespaces.md).
163
165
 
164
166
  ### Compaction And Typing Status
165
167
 
@@ -215,6 +217,8 @@ Unknown callback data outside owned prefixes is forwarded as `[callback] <data>`
215
217
 
216
218
  Extension callbacks must avoid `pi-telegram` owned prefixes such as `compact:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `settings:`, and `section:`. Workflow-specific Telegram slash commands should use the public command registry instead of becoming new core built-ins unless they are bridge lifecycle, transport ownership, queue safety, or essential operator controls.
217
219
 
220
+ The bridge does not mirror arbitrary `ctx.ui.confirm/input/select/custom` prompts from other extensions into Telegram. Companion extensions that need Telegram operation should expose a Telegram-native command, section, settings row, callback, status line, inbound/update handler, or assistant action-markup path instead of relying on hidden TUI-only prompts.
221
+
218
222
  ## Diagnostics And Operational Behavior
219
223
 
220
224
  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.
@@ -223,7 +227,7 @@ Queue reactions are shortcut controls for waiting turns. Promotion reactions (`
223
227
 
224
228
  `/telegram-status` records grouped diagnostics for transport/API, polling/update, prompt dispatch, controls, typing, compaction, setup, session lifecycle, attachment queue/delivery, and recent redacted runtime events. Expected preview noise such as unchanged edit responses is filtered out.
225
229
 
226
- When proactive push is enabled, successful local non-Telegram final replies are sent to the paired chat. Local prompt text is not mirrored because the bot does not own terminal user messages.
230
+ When proactive push is enabled and this instance owns the Telegram lock, successful local non-Telegram final replies are sent to the paired chat. Non-owners skip proactive delivery and record a runtime diagnostic. Local prompt text is not mirrored because the bot does not own terminal user messages.
227
231
 
228
232
  Telegram prompt guidance asks assistants to keep dense mobile-visible text around 37 display cells where possible, because emoji and wide Unicode make raw character counts misleading.
229
233
 
@@ -238,6 +242,14 @@ When `/model` is used during an active Telegram-owned run, the bridge can emulat
238
242
 
239
243
  This is limited to Telegram-owned runs. If π is busy with non-Telegram work, the bridge refuses the switch instead of hijacking unrelated activity.
240
244
 
245
+ ## Shutdown And Timer Lifecycle
246
+
247
+ `session_shutdown` is the hard boundary for session-bound runtime work. It suspends Telegram polling through the locked polling runtime, aborts the poll controller, stops native typing, unbinds deferred queue dispatch, clears pending media/text-group input, clears preview state, clears active turns, and drops the active abort handler.
248
+
249
+ Non-critical timers are `unref()`ed so print/headless processes are not kept alive only by Telegram housekeeping. This includes typing keepalive intervals, bounded typing-idle waits, deferred queue dispatch, media/text-group debounce windows, preview flush timers, and polling retry sleeps. Polling retry sleep is abort-aware, so shutdown does not wait for the normal retry delay after a polling error.
250
+
251
+ Non-interactive `pi -p` runs must remain passive unless π provides a live Telegram session lifecycle. Loading the extension with `telegram.json`, proactive push settings, or existing lock state must not by itself keep the print-mode process alive or let a non-owner send proactive Telegram output.
252
+
241
253
  ## Related
242
254
 
243
255
  - [README.md](../README.md)
package/docs/locks.md CHANGED
@@ -123,7 +123,7 @@ Avoid repeating the extension name in the body. Color is encouraged: extension t
123
123
 
124
124
  The previous owner may use `fs.watch`, mtime polling, or an existing status/timer tick. Long-lived watchers should compare against a snapshotted `pid`/`cwd` identity rather than a live pi context object, because session replacement such as `/new` makes captured contexts stale. The important contract is graceful singleton-runtime shutdown after ownership mismatch while session-local state that does not require polling remains owned by its original instance.
125
125
 
126
- For `pi-telegram`, direct local/TUI delivery tools (`telegram_message` and no-active-turn `telegram_attach`) are singleton-controlled and require current `/telegram-connect` ownership. They must fail when the lock is inactive or active elsewhere. Already accepted Telegram-turn reply delivery, previews, queued attachments, and queue finalization remain session-local and may complete after polling ownership moves away.
126
+ For `pi-telegram`, direct local/TUI delivery tools (`telegram_message` and no-active-turn `telegram_attach`) and proactive local/headless final-result push are singleton-controlled and require current `/telegram-connect` ownership. They must fail or skip delivery when the lock is inactive or active elsewhere. Already accepted Telegram-turn reply delivery, previews, queued attachments, and queue finalization remain session-local and may complete after polling ownership moves away.
127
127
 
128
128
  ## Reset
129
129
 
package/index.ts CHANGED
@@ -452,7 +452,6 @@ export default function (pi: Pi.ExtensionAPI) {
452
452
  ...sessionLifecycleRuntime,
453
453
  onModelSelect: currentModelRuntime.onModelSelect,
454
454
  },
455
- queueSessionLifecycle,
456
455
  configStore,
457
456
  abort,
458
457
  typing,
package/lib/bindings.ts CHANGED
@@ -113,10 +113,6 @@ interface TelegramLifecycleBindingDeps {
113
113
  Lifecycle.TelegramLifecycleRegistrationDeps,
114
114
  "onSessionStart" | "onSessionShutdown" | "onModelSelect"
115
115
  >;
116
- queueSessionLifecycle: Pick<
117
- Lifecycle.TelegramLifecycleRegistrationDeps,
118
- "onSessionShutdown"
119
- >;
120
116
  configStore: Pick<Config.TelegramConfigStore, "getOutboundHandlers">;
121
117
  abort: Runtime.TelegramRuntimeAbortPort;
122
118
  typing: Runtime.TelegramRuntimeTypingPort;
@@ -190,7 +186,6 @@ interface TelegramLifecycleBindingDeps {
190
186
  export function registerTelegramLifecycleRuntimeHooks({
191
187
  pi,
192
188
  sessionLifecycleRuntime,
193
- queueSessionLifecycle,
194
189
  configStore,
195
190
  abort,
196
191
  typing,
@@ -283,6 +278,7 @@ export function registerTelegramLifecycleRuntimeHooks({
283
278
  sendOutboundReplyArtifacts: outboundReplyArtifactSender,
284
279
  getDefaultChatId: proactivePushChatIdGetter,
285
280
  isProactivePushEnabled,
281
+ canSendProactivePush: lockOwnershipGuard.ownsContext,
286
282
  recordRuntimeEvent,
287
283
  getActiveToolExecutions: lifecycle.getActiveToolExecutions,
288
284
  setActiveToolExecutions: lifecycle.setActiveToolExecutions,
@@ -315,7 +311,7 @@ export function registerTelegramLifecycleRuntimeHooks({
315
311
  ...agentLifecycleHooks,
316
312
  async onSessionShutdown(event, ctx) {
317
313
  compactionObserver.onSessionShutdown();
318
- await queueSessionLifecycle.onSessionShutdown(event, ctx);
314
+ await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
319
315
  },
320
316
  onSessionBeforeCompact: compactionObserver.onSessionBeforeCompact,
321
317
  onSessionCompact: compactionObserver.onSessionCompact,
package/lib/media.ts CHANGED
@@ -299,6 +299,7 @@ export function queueTelegramMediaGroupMessage<
299
299
  if (!state) return;
300
300
  options.dispatchMessages(state.messages, state.context);
301
301
  }, options.debounceMs);
302
+ existing.flushTimer.unref?.();
302
303
  options.groups.set(key, existing);
303
304
  return true;
304
305
  }
package/lib/polling.ts CHANGED
@@ -261,7 +261,7 @@ export interface TelegramPollLoopDeps<
261
261
  handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
262
262
  onErrorStatus: (message: string) => void;
263
263
  onStatusReset: () => void;
264
- sleep: (ms: number) => Promise<void>;
264
+ sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
265
265
  maxUpdateFailures?: number;
266
266
  }
267
267
 
@@ -278,22 +278,42 @@ export interface TelegramPollLoopRunnerDeps<
278
278
  persistConfig: () => Promise<void>;
279
279
  handleUpdate: (update: TUpdate, ctx: TContext) => Promise<void>;
280
280
  updateStatus: (ctx: TContext, message?: string) => void;
281
- sleep?: (ms: number) => Promise<void>;
281
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
282
282
  maxUpdateFailures?: number;
283
283
  }
284
284
 
285
+ export function sleepTelegramPollingRetry(
286
+ ms: number,
287
+ signal?: AbortSignal,
288
+ ): Promise<void> {
289
+ if (ms <= 0 || signal?.aborted) return Promise.resolve();
290
+ return new Promise<void>((resolve) => {
291
+ let settled = false;
292
+ let timer: ReturnType<typeof setTimeout>;
293
+ const finish = () => {
294
+ if (settled) return;
295
+ settled = true;
296
+ signal?.removeEventListener("abort", onAbort);
297
+ resolve();
298
+ };
299
+ const onAbort = () => {
300
+ clearTimeout(timer);
301
+ finish();
302
+ };
303
+ timer = setTimeout(finish, ms);
304
+ timer.unref?.();
305
+ signal?.addEventListener("abort", onAbort, { once: true });
306
+ if (signal?.aborted) onAbort();
307
+ });
308
+ }
309
+
285
310
  export function createTelegramPollLoopRunner<
286
311
  TUpdate extends TelegramUpdate,
287
312
  TContext = unknown,
288
313
  >(
289
314
  deps: TelegramPollLoopRunnerDeps<TUpdate, TContext>,
290
315
  ): (ctx: TContext, signal: AbortSignal) => Promise<void> {
291
- const sleep =
292
- deps.sleep ??
293
- ((ms: number) =>
294
- new Promise<void>((resolve) => {
295
- setTimeout(resolve, ms);
296
- }));
316
+ const sleep = deps.sleep ?? sleepTelegramPollingRetry;
297
317
  return (ctx, signal) =>
298
318
  runTelegramPollLoop({
299
319
  ctx,
@@ -393,7 +413,8 @@ export async function runTelegramPollLoop<
393
413
  deps.recordRuntimeEvent?.("polling", error, { phase: "loop" });
394
414
  }
395
415
  deps.onErrorStatus(getTelegramPollingErrorMessage(error));
396
- await deps.sleep(3000);
416
+ await deps.sleep(3000, deps.signal);
417
+ if (deps.signal.aborted) return;
397
418
  deps.onStatusReset();
398
419
  }
399
420
  }
package/lib/preview.ts CHANGED
@@ -467,6 +467,7 @@ export function createTelegramPreviewController<
467
467
  state.flushTimer = setTimer(() => {
468
468
  void flushTelegramPreview(chatId, getRuntimeDeps());
469
469
  }, throttleMs);
470
+ state.flushTimer.unref?.();
470
471
  },
471
472
  finalize: (chatId, replyToMessageId) =>
472
473
  finalizeTelegramPreview(chatId, getRuntimeDeps(replyToMessageId)),
package/lib/queue.ts CHANGED
@@ -842,6 +842,7 @@ export interface TelegramAgentEndRuntimeDeps<
842
842
  ) => Promise<void>;
843
843
  getDefaultChatId?: () => number | undefined;
844
844
  isProactivePushEnabled?: () => boolean;
845
+ canSendProactivePush?: () => boolean;
845
846
  recordRuntimeEvent?: (
846
847
  category: string,
847
848
  error: unknown,
@@ -888,6 +889,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
888
889
  sendOutboundReplyArtifacts?: TelegramAgentEndRuntimeDeps<TTurn>["sendOutboundReplyArtifacts"];
889
890
  getDefaultChatId?: TelegramAgentEndRuntimeDeps<TTurn>["getDefaultChatId"];
890
891
  isProactivePushEnabled?: TelegramAgentEndRuntimeDeps<TTurn>["isProactivePushEnabled"];
892
+ canSendProactivePush?: (ctx: TContext) => boolean;
891
893
  recordRuntimeEvent?: TelegramAgentEndRuntimeDeps<TTurn>["recordRuntimeEvent"];
892
894
  }
893
895
 
@@ -980,6 +982,7 @@ export function createTelegramAgentEndHook<
980
982
  ): Promise<void> {
981
983
  const turn = deps.getActiveTurn();
982
984
  const proactiveEnabled = deps.isProactivePushEnabled?.() ?? false;
985
+ const canProactivePush = deps.canSendProactivePush?.(ctx) ?? false;
983
986
  await handleTelegramAgentEndRuntime({
984
987
  turn,
985
988
  assistant:
@@ -1005,6 +1008,7 @@ export function createTelegramAgentEndHook<
1005
1008
  sendOutboundReplyArtifacts: deps.sendOutboundReplyArtifacts,
1006
1009
  getDefaultChatId: deps.getDefaultChatId,
1007
1010
  isProactivePushEnabled: deps.isProactivePushEnabled,
1011
+ canSendProactivePush: () => canProactivePush,
1008
1012
  recordRuntimeEvent: deps.recordRuntimeEvent,
1009
1013
  });
1010
1014
  };
@@ -1057,20 +1061,26 @@ export async function handleTelegramAgentEndRuntime<
1057
1061
  foldQueuedPromptsIntoHistory: deps.foldQueuedPromptsIntoHistory,
1058
1062
  });
1059
1063
  if (!turn) {
1060
- if (
1061
- deps.isProactivePushEnabled?.() &&
1062
- finalText &&
1063
- !assistant.errorMessage
1064
- ) {
1065
- const defaultChatId = deps.getDefaultChatId?.();
1066
- if (defaultChatId !== undefined) {
1067
- try {
1068
- await deps.sendMarkdownReply(defaultChatId, undefined, finalText);
1069
- } catch (error) {
1070
- deps.recordRuntimeEvent?.("proactive-push", error, {
1071
- chatId: defaultChatId,
1072
- });
1064
+ const proactiveEnabled = deps.isProactivePushEnabled?.() ?? false;
1065
+ const canProactivePush = deps.canSendProactivePush?.() ?? false;
1066
+ if (proactiveEnabled && finalText && !assistant.errorMessage) {
1067
+ if (canProactivePush) {
1068
+ const defaultChatId = deps.getDefaultChatId?.();
1069
+ if (defaultChatId !== undefined) {
1070
+ try {
1071
+ await deps.sendMarkdownReply(defaultChatId, undefined, finalText);
1072
+ } catch (error) {
1073
+ deps.recordRuntimeEvent?.("proactive-push", error, {
1074
+ chatId: defaultChatId,
1075
+ });
1076
+ }
1073
1077
  }
1078
+ } else {
1079
+ deps.recordRuntimeEvent?.(
1080
+ "proactive-push",
1081
+ new Error("Proactive push skipped because this instance does not own Telegram polling."),
1082
+ { phase: "ownership" },
1083
+ );
1074
1084
  }
1075
1085
  }
1076
1086
  if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
@@ -1802,6 +1812,7 @@ export function createTelegramDeferredQueueDispatchRuntime<TContext = unknown>(
1802
1812
  return;
1803
1813
  dispatchNextQueuedTelegramTurn(boundContext);
1804
1814
  }, delayMs);
1815
+ timer.unref?.();
1805
1816
  timers.add(timer);
1806
1817
  },
1807
1818
  };
package/lib/runtime.ts CHANGED
@@ -406,6 +406,7 @@ export function startTelegramTypingLoop(
406
406
  };
407
407
  sendTyping();
408
408
  state.typingInterval = setInterval(sendTyping, deps.intervalMs);
409
+ state.typingInterval.unref?.();
409
410
  return true;
410
411
  }
411
412
 
@@ -431,7 +432,8 @@ export async function waitForTelegramTypingLoopIdle(
431
432
  await Promise.race([
432
433
  inFlight,
433
434
  new Promise<void>((resolve) => {
434
- setTimeout(resolve, timeoutMs);
435
+ const timer = setTimeout(resolve, timeoutMs);
436
+ timer.unref?.();
435
437
  }),
436
438
  ]);
437
439
  }
package/lib/status.ts CHANGED
@@ -104,6 +104,7 @@ export interface TelegramRuntimeEventRecorderOptions {
104
104
  }
105
105
 
106
106
  export interface TelegramBridgeStatusLineState {
107
+ hasBotToken?: boolean;
107
108
  botUsername?: string;
108
109
  allowedUserId?: number;
109
110
  lockState?: string;
@@ -488,6 +489,7 @@ export function createTelegramBridgeStatusRuntime<
488
489
  getBridgeStatusLineState: () => {
489
490
  const config = deps.getConfig();
490
491
  return {
492
+ hasBotToken: Boolean(config.botToken),
491
493
  botUsername: config.botUsername,
492
494
  allowedUserId: config.allowedUserId,
493
495
  lockState: deps.getRuntimeLockState?.(),
@@ -552,6 +554,13 @@ export function buildTelegramStatusBarText(
552
554
  return `${label} ${theme.fg("success", "connected")}`;
553
555
  }
554
556
 
557
+ function formatTelegramBridgeBotStatus(
558
+ state: Pick<TelegramBridgeStatusLineState, "hasBotToken" | "botUsername">,
559
+ ): string {
560
+ if (state.botUsername) return `@${state.botUsername}`;
561
+ return state.hasBotToken ? "unknown" : "not configured";
562
+ }
563
+
555
564
  export function buildTelegramBridgeStatusLines(
556
565
  state: TelegramBridgeStatusLineState,
557
566
  ): string[] {
@@ -566,7 +575,7 @@ export function buildTelegramBridgeStatusLines(
566
575
  ).length;
567
576
  return [
568
577
  "connection:",
569
- `- bot: ${state.botUsername ? `@${state.botUsername}` : "not configured"}`,
578
+ `- bot: ${formatTelegramBridgeBotStatus(state)}`,
570
579
  `- allowed user: ${state.allowedUserId ?? "not paired"}`,
571
580
  ...(state.lockState ? [`- owner: ${state.lockState}`] : []),
572
581
  "",
@@ -132,6 +132,7 @@ export function queueTelegramTextGroupMessage<
132
132
  if (!queued || queued.context === undefined) return;
133
133
  options.dispatchMessages(queued.messages, queued.context);
134
134
  }, options.debounceMs);
135
+ state.flushTimer.unref?.();
135
136
  options.groups.set(key, state);
136
137
  return true;
137
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.16.1",
3
+ "version": "0.16.3",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/screenshot.png CHANGED
Binary file