@llblab/pi-telegram 0.24.0 → 0.24.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/lib/locks.ts CHANGED
@@ -22,7 +22,8 @@ import { basename, dirname, join } from "node:path";
22
22
  import { resolveTelegramOwnersPath } from "./paths.ts";
23
23
 
24
24
  export const TELEGRAM_LOCK_KEY = "default";
25
- export const TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS = 5_000;
25
+ export const TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS = 8_000;
26
+ const TELEGRAM_OWNERSHIP_REFRESH_MS = 2_000;
26
27
  const TELEGRAM_LOCK_WRITE_RETRY_ATTEMPTS = 5;
27
28
  const TELEGRAM_LOCK_WRITE_RETRY_DELAY_MS = 25;
28
29
  const TELEGRAM_LOCK_TRANSACTION_ATTEMPTS = 80;
@@ -181,8 +182,7 @@ interface TelegramLockTransactionOwner {
181
182
  generation: string;
182
183
  }
183
184
 
184
- const TELEGRAM_TRANSACTION_OWNER_PATTERN =
185
- /^owner\.([A-Za-z0-9-]+)\.json$/u;
185
+ const TELEGRAM_TRANSACTION_OWNER_PATTERN = /^owner\.([A-Za-z0-9-]+)\.json$/u;
186
186
 
187
187
  function getLockTransactionOwnerFile(generation: string): string {
188
188
  return `owner.${generation}.json`;
@@ -350,6 +350,8 @@ type TelegramTransactionGlobal = typeof globalThis & {
350
350
 
351
351
  export interface TelegramFileTransactionOptions {
352
352
  recoveryRename?: typeof renameSync;
353
+ attempts?: number;
354
+ retryDelayMs?: number;
353
355
  }
354
356
 
355
357
  function getActiveTransactionReclaims(): Set<string> {
@@ -366,7 +368,13 @@ function reclaimAbandonedDirectoryGuard(
366
368
  } catch {
367
369
  return false;
368
370
  }
369
- const entries = readdirSync(path);
371
+ let entries: string[];
372
+ try {
373
+ entries = readdirSync(path);
374
+ } catch {
375
+ // Another recovery candidate may remove the observed guard after lstat.
376
+ return false;
377
+ }
370
378
  if (entries.length !== 1) return false;
371
379
  const entry = entries[0];
372
380
  let observedPid: number;
@@ -382,10 +390,7 @@ function reclaimAbandonedDirectoryGuard(
382
390
  observedReclaimGeneration = match[2];
383
391
  }
384
392
  const activeReclaims = getActiveTransactionReclaims();
385
- if (
386
- observedPid === process.pid &&
387
- observedReclaimGeneration !== undefined
388
- ) {
393
+ if (observedPid === process.pid && observedReclaimGeneration !== undefined) {
389
394
  if (activeReclaims.has(observedReclaimGeneration)) return false;
390
395
  } else if (isProcessAlive(observedPid)) {
391
396
  return false;
@@ -564,10 +569,7 @@ function recoverAbandonedLockTransaction(
564
569
  }
565
570
 
566
571
  const recoveryGuardPath = `${path}.recovery`;
567
- const recoveryOwner = acquireLegacyRecoveryGuard(
568
- recoveryGuardPath,
569
- options,
570
- );
572
+ const recoveryOwner = acquireLegacyRecoveryGuard(recoveryGuardPath, options);
571
573
  if (!recoveryOwner) return undefined;
572
574
  let recoveredOwner: TelegramLockTransactionOwner | undefined;
573
575
  try {
@@ -606,24 +608,28 @@ function acquireLockTransaction(
606
608
  path: string,
607
609
  options: TelegramFileTransactionOptions = {},
608
610
  ): TelegramLockTransactionOwner {
611
+ const attempts = Math.max(
612
+ 1,
613
+ options.attempts ?? TELEGRAM_LOCK_TRANSACTION_ATTEMPTS,
614
+ );
615
+ const retryDelayMs = Math.max(
616
+ 0,
617
+ options.retryDelayMs ?? TELEGRAM_LOCK_TRANSACTION_RETRY_DELAY_MS,
618
+ );
609
619
  mkdirSync(dirname(path), { recursive: true });
610
- for (
611
- let attempt = 0;
612
- attempt < TELEGRAM_LOCK_TRANSACTION_ATTEMPTS;
613
- attempt += 1
614
- ) {
620
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
615
621
  try {
616
622
  return createLockTransactionGuard(path);
617
623
  } catch (error) {
618
624
  if (!isLockTransactionContentionError(error, path)) throw error;
619
625
  const recoveredOwner = recoverAbandonedLockTransaction(path, options);
620
626
  if (recoveredOwner !== undefined) return recoveredOwner;
621
- if (attempt === TELEGRAM_LOCK_TRANSACTION_ATTEMPTS - 1) {
627
+ if (attempt === attempts - 1) {
622
628
  throw new Error(
623
629
  `Timed out acquiring Telegram lock transaction: ${path}`,
624
630
  );
625
631
  }
626
- sleepSync(TELEGRAM_LOCK_TRANSACTION_RETRY_DELAY_MS);
632
+ sleepSync(retryDelayMs);
627
633
  }
628
634
  }
629
635
  throw new Error(`Failed to acquire Telegram lock transaction: ${path}`);
@@ -1140,6 +1146,7 @@ export interface TelegramLockedPollingRuntimeDeps<
1140
1146
  details?: Record<string, unknown>,
1141
1147
  ) => void;
1142
1148
  ownershipCheckMs?: number;
1149
+ ownershipRefreshMs?: number;
1143
1150
  }
1144
1151
 
1145
1152
  function snapshotLockContext(ctx: TelegramLockContext): TelegramLockContext {
@@ -1151,16 +1158,20 @@ export function createTelegramLockedPollingRuntime<
1151
1158
  >(
1152
1159
  deps: TelegramLockedPollingRuntimeDeps<TContext>,
1153
1160
  ): TelegramLockedPollingRuntime<TContext> {
1154
- let ownershipInterval: ReturnType<typeof setInterval> | undefined;
1161
+ let ownershipCheckInterval: ReturnType<typeof setInterval> | undefined;
1162
+ let ownershipRefreshInterval: ReturnType<typeof setInterval> | undefined;
1155
1163
  let ownershipStop: Promise<void> | undefined;
1156
1164
  let takeoverCandidate: TelegramLockEntry | undefined;
1157
1165
  let sessionAutoStartRun: Promise<void> | undefined;
1158
1166
  let sessionAutoStartGeneration = 0;
1159
1167
  const ownershipCheckMs = deps.ownershipCheckMs ?? 1000;
1168
+ const ownershipRefreshMs =
1169
+ deps.ownershipRefreshMs ?? TELEGRAM_OWNERSHIP_REFRESH_MS;
1160
1170
  const stopOwnershipWatcher = () => {
1161
- if (!ownershipInterval) return;
1162
- clearInterval(ownershipInterval);
1163
- ownershipInterval = undefined;
1171
+ if (ownershipCheckInterval) clearInterval(ownershipCheckInterval);
1172
+ if (ownershipRefreshInterval) clearInterval(ownershipRefreshInterval);
1173
+ ownershipCheckInterval = undefined;
1174
+ ownershipRefreshInterval = undefined;
1164
1175
  };
1165
1176
  const suspendPolling = async () => {
1166
1177
  sessionAutoStartGeneration += 1;
@@ -1190,15 +1201,24 @@ export function createTelegramLockedPollingRuntime<
1190
1201
  const startOwnershipWatcher = (ctx: TContext) => {
1191
1202
  const owner = snapshotLockContext(ctx);
1192
1203
  stopOwnershipWatcher();
1193
- ownershipInterval = setInterval(() => {
1204
+ ownershipCheckInterval = setInterval(() => {
1205
+ try {
1206
+ if (deps.lock.owns(owner)) return;
1207
+ } catch (error) {
1208
+ deps.recordRuntimeEvent?.("lock", error, { phase: "check" });
1209
+ }
1210
+ stopAfterOwnershipLoss();
1211
+ }, ownershipCheckMs);
1212
+ ownershipRefreshInterval = setInterval(() => {
1194
1213
  try {
1195
1214
  if (deps.lock.refresh(owner)) return;
1196
1215
  } catch (error) {
1197
1216
  deps.recordRuntimeEvent?.("lock", error, { phase: "refresh" });
1198
1217
  }
1199
1218
  stopAfterOwnershipLoss();
1200
- }, ownershipCheckMs);
1201
- ownershipInterval.unref?.();
1219
+ }, ownershipRefreshMs);
1220
+ ownershipCheckInterval.unref?.();
1221
+ ownershipRefreshInterval.unref?.();
1202
1222
  };
1203
1223
  const runOwnedPollingStart = async (
1204
1224
  ctx: TContext,
package/lib/logs.ts CHANGED
@@ -102,6 +102,12 @@ export function createTelegramRuntimeJsonlLog(
102
102
  const getNowMs = options.getNowMs ?? Date.now;
103
103
  const scopeKeys = new Map<string, string | undefined>();
104
104
  let pending: Promise<void> = Promise.resolve();
105
+ let appendScheduled = false;
106
+ let queuedAppends: {
107
+ path: string;
108
+ previousPath: string;
109
+ line: string;
110
+ }[] = [];
105
111
 
106
112
  const ensureParent = (path: string) => {
107
113
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
@@ -156,27 +162,79 @@ export function createTelegramRuntimeJsonlLog(
156
162
  };
157
163
 
158
164
  const appendLine = (line: string) => {
159
- const path = resolvePath();
160
- const previousPath = resolvePreviousPath();
165
+ queuedAppends.push({
166
+ path: resolvePath(),
167
+ previousPath: resolvePreviousPath(),
168
+ line,
169
+ });
170
+ if (appendScheduled) return;
171
+ appendScheduled = true;
161
172
  pending = pending
162
173
  .then(() => {
163
- ensureParent(path);
164
- withTelegramFileTransaction(`${path}.transaction`, () => {
165
- if (
166
- existsSync(path) &&
167
- statSync(path).size > maxBytes &&
168
- (!options.canReset || options.canReset())
169
- ) {
170
- const rotate = () =>
171
- writeResetLocked(path, previousPath, "max-bytes", { maxBytes });
172
- if (options.commitReset) {
173
- options.commitReset(rotate);
174
- } else {
175
- rotate();
176
- }
174
+ appendScheduled = false;
175
+ const batch = queuedAppends;
176
+ queuedAppends = [];
177
+ const groups = new Map<
178
+ string,
179
+ { path: string; previousPath: string; lines: string[] }
180
+ >();
181
+ for (const entry of batch) {
182
+ const key = `${entry.path}\u0000${entry.previousPath}`;
183
+ const group = groups.get(key);
184
+ if (group) group.lines.push(entry.line);
185
+ else groups.set(key, { ...entry, lines: [entry.line] });
186
+ }
187
+ for (const group of groups.values()) {
188
+ try {
189
+ ensureParent(group.path);
190
+ withTelegramFileTransaction(`${group.path}.transaction`, () => {
191
+ let currentSize = existsSync(group.path)
192
+ ? statSync(group.path).size
193
+ : 0;
194
+ let chunk = "";
195
+ let chunkBytes = 0;
196
+ const flushChunk = () => {
197
+ if (!chunk) return;
198
+ appendFileSync(group.path, chunk, { mode: 0o600 });
199
+ currentSize += chunkBytes;
200
+ chunk = "";
201
+ chunkBytes = 0;
202
+ };
203
+ const rotate = (): boolean => {
204
+ if (options.canReset && !options.canReset()) return false;
205
+ let rotated = false;
206
+ const commit = () => {
207
+ writeResetLocked(
208
+ group.path,
209
+ group.previousPath,
210
+ "max-bytes",
211
+ { maxBytes },
212
+ );
213
+ rotated = true;
214
+ };
215
+ if (options.commitReset) options.commitReset(commit);
216
+ else commit();
217
+ if (rotated) currentSize = statSync(group.path).size;
218
+ return rotated;
219
+ };
220
+ for (const line of group.lines) {
221
+ const lineBytes = Buffer.byteLength(line);
222
+ if (
223
+ currentSize + chunkBytes > 0 &&
224
+ currentSize + chunkBytes + lineBytes > maxBytes
225
+ ) {
226
+ flushChunk();
227
+ rotate();
228
+ }
229
+ chunk += line;
230
+ chunkBytes += lineBytes;
231
+ }
232
+ flushChunk();
233
+ });
234
+ } catch {
235
+ // Diagnostics failures for one profile must not drop other groups.
177
236
  }
178
- appendFileSync(path, line, { mode: 0o600 });
179
- });
237
+ }
180
238
  })
181
239
  .catch(() => undefined);
182
240
  };
package/lib/media.ts CHANGED
@@ -390,15 +390,6 @@ export function appendTelegramReplyContext(
390
390
  return text ? `${text}\n\n${replyContext}` : `_\n\n${replyContext}`;
391
391
  }
392
392
 
393
- export function appendTelegramForwardContext(
394
- text: string,
395
- forwardContext: string,
396
- ): string {
397
- if (!forwardContext) return text;
398
- const forwardBlock = `[forward|${forwardContext.replace(/:\s+/g, ":")}]`;
399
- return text ? `\n\n${forwardBlock} ${text}` : `\n\n${forwardBlock}`;
400
- }
401
-
402
393
  export function extractTelegramMessagePromptText(
403
394
  message: TelegramMediaMessage,
404
395
  ): string {
package/lib/menu-model.ts CHANGED
@@ -484,7 +484,11 @@ export function createTelegramModelMenuStateBuilder<
484
484
  TelegramModelMenuStateBuilderContext<TModel>,
485
485
  >(
486
486
  deps: TelegramModelMenuStateBuilderDeps<TModel, TContext>,
487
- ): (chatId: number, ctx: TContext, threadId?: number) => Promise<TelegramModelMenuState<TModel>> {
487
+ ): (
488
+ chatId: number,
489
+ ctx: TContext,
490
+ threadId?: number,
491
+ ) => Promise<TelegramModelMenuState<TModel>> {
488
492
  return async (chatId, ctx, threadId) => {
489
493
  const settingsManager = deps.createSettingsManager(ctx.cwd);
490
494
  return deps.runtime.buildState({
@@ -745,17 +749,6 @@ export function setTelegramModelScope(
745
749
  return { patterns, enabled };
746
750
  }
747
751
 
748
- export function toggleTelegramModelScope(
749
- state: TelegramModelMenuState,
750
- model: MenuModel,
751
- ): { patterns: string[]; enabled: boolean } {
752
- return setTelegramModelScope(
753
- state,
754
- model,
755
- !isTelegramModelScoped(state, model),
756
- );
757
- }
758
-
759
752
  export function buildTelegramModelCallbackPlan<
760
753
  TModel extends MenuModel = MenuModel,
761
754
  >(
package/lib/menu.ts CHANGED
@@ -118,25 +118,6 @@ export type {
118
118
  TelegramThinkingMenuOpenDeps,
119
119
  } from "./menu-thinking.ts";
120
120
 
121
- export interface TelegramMenuEffectPort<TModel extends MenuModel = MenuModel> {
122
- answerCallbackQuery: (
123
- callbackQueryId: string,
124
- text?: string,
125
- ) => Promise<void>;
126
- updateModelMenuMessage: () => Promise<void>;
127
- updateThinkingMenuMessage: () => Promise<void>;
128
- updateStatusMessage: () => Promise<void>;
129
- persistScopedModelPatterns?: (patterns: string[]) => Promise<void>;
130
- setModel: (model: TModel) => Promise<boolean>;
131
- setCurrentModel: (model: TModel) => void;
132
- setThinkingLevel: (level: ThinkingLevel) => void;
133
- getCurrentThinkingLevel: () => ThinkingLevel;
134
- stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel>) => void;
135
- restartInterruptedTelegramTurn: (
136
- selection: ScopedTelegramModel<TModel>,
137
- ) => Promise<boolean> | boolean;
138
- }
139
-
140
121
  export interface TelegramMenuCallbackEntryDeps {
141
122
  handleStatusAction: () => Promise<boolean>;
142
123
  handleThinkingAction: () => Promise<boolean>;
package/lib/outbound.ts CHANGED
@@ -197,12 +197,6 @@ export function registerTelegramOutboundHandler(
197
197
  };
198
198
  }
199
199
 
200
- export function hasTelegramOutboundHandler(kind: string): boolean {
201
- const registry = getOrCreateOutboundHandlerRegistry();
202
- const list = registry.handlers.get(kind);
203
- return !!list && list.length > 0;
204
- }
205
-
206
200
  export function getTelegramOutboundProgrammaticHandlers(
207
201
  kind: string,
208
202
  ): TelegramOutboundProgrammaticHandler[] {
@@ -953,15 +947,11 @@ export function createTelegramAssistantOutputSender<
953
947
  TReplyMarkup = unknown,
954
948
  >(deps: {
955
949
  recordOwnership?: Replies.TelegramReplyOwnershipRecorder["record"];
956
- sendMessage: (
957
- body: TelegramSendMessageBody,
958
- ) => Promise<TelegramSentMessage>;
950
+ sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
959
951
  sendRichMessage: (
960
952
  body: TelegramSendRichMessageBody,
961
953
  ) => Promise<TelegramSentMessage>;
962
- editMessage: (
963
- body: TelegramEditMessageTextBody,
964
- ) => Promise<unknown>;
954
+ editMessage: (body: TelegramEditMessageTextBody) => Promise<unknown>;
965
955
  getAssistantRenderingMode: () => "rich" | "html";
966
956
  execCommand: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>["execCommand"];
967
957
  getHandlers?: TelegramOutboundTextReplyRuntimeDeps<TReplyMarkup>["getHandlers"];
package/lib/pi.ts CHANGED
@@ -114,10 +114,6 @@ export function getSessionCompactionReason(
114
114
  : "unknown";
115
115
  }
116
116
 
117
- export type PiSendUserMessageOptions = NonNullable<
118
- Parameters<ExtensionAPI["sendUserMessage"]>[1]
119
- >;
120
-
121
117
  export interface PiExtensionApiRuntimePorts {
122
118
  sendUserMessage: ExtensionAPI["sendUserMessage"];
123
119
  exec: ExtensionAPI["exec"];
package/lib/queue.ts CHANGED
@@ -1764,7 +1764,6 @@ export async function shutdownTelegramSessionRuntime<TQueueItem>(
1764
1764
  deps.clearPreview(activeTurnChatId, target ? { target } : undefined),
1765
1765
  new Promise<void>((resolve) => {
1766
1766
  timeout = setTimeout(resolve, previewTimeoutMs);
1767
- timeout.unref?.();
1768
1767
  }),
1769
1768
  ]).finally(() => {
1770
1769
  if (timeout) clearTimeout(timeout);
@@ -2240,10 +2239,6 @@ export interface TelegramPromptDeliveryOptions {
2240
2239
  deliverAs: "followUp";
2241
2240
  }
2242
2241
 
2243
- export const TELEGRAM_PROMPT_FOLLOW_UP_DELIVERY = {
2244
- deliverAs: "followUp",
2245
- } as const satisfies TelegramPromptDeliveryOptions;
2246
-
2247
2242
  export interface TelegramDispatchRuntimeDeps<TContext = unknown> {
2248
2243
  executeControlItem: (
2249
2244
  item: Extract<
package/lib/replies.ts CHANGED
@@ -871,29 +871,6 @@ export function dedupSendTextReply(
871
871
  };
872
872
  }
873
873
 
874
- /** Wrap a sendMarkdownReply with reply dedup. */
875
- export function dedupSendMarkdownReply<TReplyMarkup = unknown>(
876
- dedup: ReplyDedupRuntime,
877
- inner: (
878
- chatId: number,
879
- replyToMessageId: number | undefined,
880
- markdown: string,
881
- options?: { replyMarkup?: TReplyMarkup },
882
- ) => Promise<number | undefined>,
883
- ): (
884
- chatId: number,
885
- replyToMessageId: number,
886
- markdown: string,
887
- options?: { replyMarkup?: TReplyMarkup },
888
- ) => Promise<number | undefined> {
889
- return async (chatId, replyToMessageId, markdown, options) => {
890
- const effectiveReplyTo = dedup.shouldReply(replyToMessageId)
891
- ? replyToMessageId
892
- : undefined;
893
- return inner(chatId, effectiveReplyTo, markdown, options);
894
- };
895
- }
896
-
897
874
  /**
898
875
  * Guest reply sender: answers guest queries with native Rich Markdown content.
899
876
  * Guest queries use InlineQueryResult input_message_content rather than chat
package/lib/runtime.ts CHANGED
@@ -508,8 +508,7 @@ export async function waitForTelegramTypingLoopIdle(
508
508
  await Promise.race([
509
509
  inFlight,
510
510
  new Promise<void>((resolve) => {
511
- const timer = setTimeout(resolve, timeoutMs);
512
- timer.unref?.();
511
+ setTimeout(resolve, timeoutMs);
513
512
  }),
514
513
  ]);
515
514
  }
package/lib/sections.ts CHANGED
@@ -358,7 +358,7 @@ export function createTelegramExtensionSectionRegistry(): TelegramSectionRegistr
358
358
  >();
359
359
  let nextToken = 0;
360
360
 
361
- const register = (section: TelegramSectionRegistration): () => void => {
361
+ const register = (section: TelegramSectionRegistration): (() => void) => {
362
362
  const duplicate = [...sections.values()].find((s) => s.id === section.id);
363
363
  if (duplicate) {
364
364
  throw new Error(`Telegram section id already registered: ${section.id}`);
@@ -708,50 +708,3 @@ export async function handleTelegramSectionSettingsOpen(
708
708
  return true;
709
709
  }
710
710
  }
711
-
712
- export async function handleTelegramSectionSettingsCallback(
713
- registry: TelegramSectionRegistry,
714
- token: TelegramSectionToken,
715
- action: string,
716
- payload: string,
717
- chatId: number,
718
- messageId: number,
719
- callbackQueryId: string,
720
- deps: TelegramSectionCallbackHandlerDeps,
721
- ): Promise<boolean> {
722
- const section = registry.getByToken(token);
723
- if (!section || !section.registration.settings?.handleCallback) {
724
- await deps.answerCallbackQuery(
725
- callbackQueryId,
726
- "This section is no longer available.",
727
- );
728
- return true;
729
- }
730
- try {
731
- const ctx = buildTelegramSectionCallbackContext(
732
- section.id,
733
- token,
734
- chatId,
735
- messageId,
736
- action,
737
- payload,
738
- callbackQueryId,
739
- deps,
740
- `settings:list`,
741
- );
742
- const result = await section.registration.settings.handleCallback(ctx);
743
- if (result === "pass") {
744
- await deps.answerCallbackQuery(callbackQueryId);
745
- }
746
- registry.clearError(token, "settings_callback");
747
- return true;
748
- } catch (error) {
749
- const message = sectionErrorMessage(error);
750
- registry.recordError(token, message, "settings_callback");
751
- await deps.answerCallbackQuery(
752
- callbackQueryId,
753
- `Section error: ${message}`,
754
- );
755
- return true;
756
- }
757
- }
package/lib/status.ts CHANGED
@@ -262,9 +262,10 @@ export interface TelegramBridgeStatusRuntimeDeps<
262
262
  statusKey?: string;
263
263
  getConfig: () => TelegramBridgeStatusConfig;
264
264
  getActiveProfileName?: () => string | undefined;
265
- getDiagnosticPaths?: (
266
- profileName?: string,
267
- ) => { state: string; logs: string };
265
+ getDiagnosticPaths?: (profileName?: string) => {
266
+ state: string;
267
+ logs: string;
268
+ };
268
269
  isPollingActive: () => boolean;
269
270
  getActiveSourceMessageIds: () => number[] | undefined;
270
271
  hasActiveTurn: () => boolean;
@@ -295,8 +296,7 @@ export interface TelegramBridgeStatusRuntimeDeps<
295
296
  TelegramBridgeStatusSyncSlice | undefined
296
297
  >;
297
298
  getThreadReconciliationState?: () =>
298
- | TelegramBridgeThreadReconciliationState
299
- | undefined;
299
+ TelegramBridgeThreadReconciliationState | undefined;
300
300
  getInstanceSlot?: () => string | undefined;
301
301
  getInstanceThreadName?: () => string | undefined;
302
302
  getNowMs?: () => number;
@@ -726,6 +726,8 @@ export function createTelegramStatusSnapshot(
726
726
  };
727
727
  }
728
728
 
729
+ const TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS = 100;
730
+
729
731
  export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps: {
730
732
  persistSnapshot: () => Promise<void>;
731
733
  recordError: (error: unknown) => void;
@@ -738,7 +740,7 @@ export function createTelegramRuntimeDiagnosticsSnapshotScheduler(deps: {
738
740
  timer = setTimer(() => {
739
741
  timer = undefined;
740
742
  void deps.persistSnapshot().catch(deps.recordError);
741
- }, 0);
743
+ }, TELEGRAM_DIAGNOSTICS_SNAPSHOT_COALESCE_MS);
742
744
  if (typeof timer !== "number") timer?.unref?.();
743
745
  };
744
746
  }
@@ -1084,7 +1086,9 @@ function buildTelegramBridgeCompactStatusLines(
1084
1086
  return [
1085
1087
  "connection:",
1086
1088
  `- bot: ${formatTelegramBridgeBotStatus(state)}`,
1087
- ...(state.activeProfileName ? [`- profile: ${state.activeProfileName}`] : []),
1089
+ ...(state.activeProfileName
1090
+ ? [`- profile: ${state.activeProfileName}`]
1091
+ : []),
1088
1092
  `- user: ${state.allowedUserId ?? "not paired"}`,
1089
1093
  ...(state.botThreadMode ? [`- thread mode: ${state.botThreadMode}`] : []),
1090
1094
  ...(state.busRole ? [`- role: ${state.busRole}`] : []),
@@ -1139,7 +1143,9 @@ export function buildTelegramBridgeDiagnosticStatusLines(
1139
1143
  return [
1140
1144
  "connection:",
1141
1145
  `- bot: ${formatTelegramBridgeBotStatus(state)}`,
1142
- ...(state.activeProfileName ? [`- profile: ${state.activeProfileName}`] : []),
1146
+ ...(state.activeProfileName
1147
+ ? [`- profile: ${state.activeProfileName}`]
1148
+ : []),
1143
1149
  `- allowed user: ${state.allowedUserId ?? "not paired"}`,
1144
1150
  ...(state.botThreadMode
1145
1151
  ? [
@@ -297,10 +297,6 @@ export type TelegramSendRichMessageBody = Record<string, unknown> & {
297
297
  reply_parameters?: TelegramReplyParameters;
298
298
  };
299
299
 
300
- export type TelegramInputRichMessageContent = {
301
- rich_message: TelegramInputRichMessage;
302
- };
303
-
304
300
  export type TelegramEditMessageTextBody = Record<string, unknown> & {
305
301
  chat_id: number;
306
302
  message_id: number;