@llblab/pi-telegram 0.18.5 → 0.19.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.
@@ -18,6 +18,7 @@ export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
18
18
 
19
19
  export interface TelegramSettingsStateDeps {
20
20
  isProactivePushEnabled: () => boolean;
21
+ areRichDraftPreviewsEnabled: () => boolean;
21
22
  getTimeInjectionMode: () => TelegramTimeMode;
22
23
  getVoiceReplyMode: () => TelegramVoiceReplyMode;
23
24
  isVoiceReplyModeConfigured: () => boolean;
@@ -25,6 +26,7 @@ export interface TelegramSettingsStateDeps {
25
26
 
26
27
  export interface TelegramSettingsMutationDeps extends TelegramSettingsStateDeps {
27
28
  setProactivePushEnabled: (enabled: boolean) => Promise<void>;
29
+ setRichDraftPreviewsEnabled: (enabled: boolean) => Promise<void>;
28
30
  setVoiceReplyMode: (
29
31
  mode: TelegramVoiceReplyMode | undefined,
30
32
  ) => Promise<void>;
@@ -116,6 +118,8 @@ export interface TelegramSettingsMenuRuntimeDeps<
116
118
 
117
119
  export const SETTINGS_MENU_TITLE = "<b>⚙️ Settings:</b>";
118
120
  export const PROACTIVE_PUSH_SETTINGS_TITLE = "<b>📌 Proactive push:</b>";
121
+ export const RICH_DRAFT_PREVIEWS_SETTINGS_TITLE =
122
+ "<b>📝 Rich draft previews:</b>";
119
123
  export const TIME_INJECTION_MODE_SETTINGS_TITLE =
120
124
  "<b>🕒 Time injection mode:</b>";
121
125
  export const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>👄 Voice reply mode:</b>";
@@ -151,6 +155,17 @@ export function buildProactivePushSettingsText(
151
155
  ].join("\n");
152
156
  }
153
157
 
158
+ export function buildRichDraftPreviewsSettingsText(enabled: boolean): string {
159
+ return [
160
+ `${RICH_DRAFT_PREVIEWS_SETTINGS_TITLE} <code>${enabled ? "on" : "off"}</code>`,
161
+ "",
162
+ "Show progressive Rich Markdown draft previews while the model is answering.",
163
+ "",
164
+ "<code>-</code> <code>off</code> (default): show native active status, then send the final Rich Markdown answer.",
165
+ "<code>-</code> <code>on</code>: also stream safe Rich Markdown draft previews before the final answer.",
166
+ ].join("\n");
167
+ }
168
+
154
169
  export function buildVoiceReplyModeSettingsText(
155
170
  mode: TelegramVoiceReplyMode,
156
171
  configured = true,
@@ -185,6 +200,7 @@ export function buildTimeInjectionModeSettingsText(
185
200
 
186
201
  export function buildTelegramSettingsMenuReplyMarkup(
187
202
  proactivePushEnabled: boolean,
203
+ richDraftPreviewsEnabled: boolean,
188
204
  voiceReplyMode: TelegramVoiceReplyMode,
189
205
  timeInjectionMode: TelegramTimeMode,
190
206
  sectionRegistry?: TelegramSectionRegistry,
@@ -217,6 +233,12 @@ export function buildTelegramSettingsMenuReplyMarkup(
217
233
  callback_data: "settings:open:time-injection",
218
234
  },
219
235
  ],
236
+ [
237
+ {
238
+ text: `📝 Rich drafts: ${richDraftPreviewsEnabled ? "on" : "off"}`,
239
+ callback_data: "settings:open:rich-drafts",
240
+ },
241
+ ],
220
242
  [
221
243
  {
222
244
  text: `📌 Proactive push: ${proactivePushEnabled ? "on" : "off"}`,
@@ -239,6 +261,7 @@ export async function openTelegramSettingsMenu<
239
261
  buildTelegramSettingsMenuText(),
240
262
  buildTelegramSettingsMenuReplyMarkup(
241
263
  deps.isProactivePushEnabled(),
264
+ deps.areRichDraftPreviewsEnabled(),
242
265
  deps.getVoiceReplyMode(),
243
266
  deps.getTimeInjectionMode(),
244
267
  sectionRegistry,
@@ -271,6 +294,26 @@ export function buildProactivePushSettingsReplyMarkup(
271
294
  };
272
295
  }
273
296
 
297
+ export function buildRichDraftPreviewsSettingsReplyMarkup(
298
+ enabled: boolean,
299
+ ): TelegramSettingsMenuReplyMarkup {
300
+ return {
301
+ inline_keyboard: [
302
+ [{ text: "⬆️ Back", callback_data: "settings:list" }],
303
+ [
304
+ {
305
+ text: enabled ? "🟢 On" : "⚫️ On",
306
+ callback_data: "settings:set:rich-drafts:on",
307
+ },
308
+ {
309
+ text: enabled ? "⚫️ Off" : "🟡 Off",
310
+ callback_data: "settings:set:rich-drafts:off",
311
+ },
312
+ ],
313
+ ],
314
+ };
315
+ }
316
+
274
317
  export function buildTimeInjectionModeSettingsReplyMarkup(
275
318
  mode: TelegramTimeMode,
276
319
  ): TelegramSettingsMenuReplyMarkup {
@@ -320,6 +363,7 @@ export async function updateTelegramSettingsMenuMessage(
320
363
  buildTelegramSettingsMenuText(),
321
364
  buildTelegramSettingsMenuReplyMarkup(
322
365
  deps.isProactivePushEnabled(),
366
+ deps.areRichDraftPreviewsEnabled(),
323
367
  deps.getVoiceReplyMode(),
324
368
  deps.getTimeInjectionMode(),
325
369
  sectionRegistry,
@@ -338,6 +382,16 @@ export async function updateProactivePushSettingsMessage(
338
382
  );
339
383
  }
340
384
 
385
+ export async function updateRichDraftPreviewsSettingsMessage(
386
+ deps: TelegramSettingsMenuCallbackDeps,
387
+ ): Promise<void> {
388
+ const enabled = deps.areRichDraftPreviewsEnabled();
389
+ await deps.updateSettingsMessage(
390
+ buildRichDraftPreviewsSettingsText(enabled),
391
+ buildRichDraftPreviewsSettingsReplyMarkup(enabled),
392
+ );
393
+ }
394
+
341
395
  export async function updateTimeInjectionModeSettingsMessage(
342
396
  deps: TelegramSettingsMenuCallbackDeps,
343
397
  ): Promise<void> {
@@ -375,6 +429,11 @@ export async function handleTelegramSettingsMenuCallbackAction(
375
429
  await deps.answerCallbackQuery(callbackQueryId);
376
430
  return true;
377
431
  }
432
+ if (data === "settings:open:rich-drafts") {
433
+ await updateRichDraftPreviewsSettingsMessage(deps);
434
+ await deps.answerCallbackQuery(callbackQueryId);
435
+ return true;
436
+ }
378
437
  if (data === "settings:open:voice-reply") {
379
438
  await updateVoiceReplyModeSettingsMessage(deps);
380
439
  await deps.answerCallbackQuery(callbackQueryId);
@@ -427,6 +486,19 @@ export async function handleTelegramSettingsMenuCallbackAction(
427
486
  return true;
428
487
  }
429
488
  }
489
+ if (
490
+ data === "settings:set:rich-drafts:on" ||
491
+ data === "settings:set:rich-drafts:off"
492
+ ) {
493
+ const enabled = data.endsWith(":on");
494
+ await deps.setRichDraftPreviewsEnabled(enabled);
495
+ await updateRichDraftPreviewsSettingsMessage(deps);
496
+ await deps.answerCallbackQuery(
497
+ callbackQueryId,
498
+ `Rich draft previews ${enabled ? "enabled" : "disabled"}`,
499
+ );
500
+ return true;
501
+ }
430
502
  if (
431
503
  data === "settings:set:proactive:on" ||
432
504
  data === "settings:set:proactive:off"
@@ -457,6 +529,7 @@ export function createTelegramSettingsMenuRuntime<
457
529
  {
458
530
  getModelMenuState: () => deps.getModelMenuState(chatId, ctx),
459
531
  isProactivePushEnabled: deps.isProactivePushEnabled,
532
+ areRichDraftPreviewsEnabled: deps.areRichDraftPreviewsEnabled,
460
533
  getVoiceReplyMode: deps.getVoiceReplyMode,
461
534
  isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
462
535
  getTimeInjectionMode: deps.getTimeInjectionMode,
@@ -475,6 +548,7 @@ export function createTelegramSettingsMenuRuntime<
475
548
  updateTelegramSettingsMenuMessage(
476
549
  {
477
550
  isProactivePushEnabled: deps.isProactivePushEnabled,
551
+ areRichDraftPreviewsEnabled: deps.areRichDraftPreviewsEnabled,
478
552
  getVoiceReplyMode: deps.getVoiceReplyMode,
479
553
  isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
480
554
  getTimeInjectionMode: deps.getTimeInjectionMode,
@@ -513,6 +587,18 @@ export function createTelegramSettingsMenuRuntime<
513
587
  );
514
588
  return true;
515
589
  }
590
+ if (
591
+ query.data === "settings:set:rich-drafts:on" ||
592
+ query.data === "settings:set:rich-drafts:off"
593
+ ) {
594
+ const enabled = query.data.endsWith(":on");
595
+ await deps.setRichDraftPreviewsEnabled(enabled);
596
+ await deps.answerCallbackQuery(
597
+ query.id,
598
+ `Rich draft previews ${enabled ? "enabled" : "disabled"}`,
599
+ );
600
+ return true;
601
+ }
516
602
  const hasTimeInjectionPrefix = query.data.startsWith(
517
603
  "settings:set:time-injection:",
518
604
  );
@@ -543,10 +629,12 @@ export function createTelegramSettingsMenuRuntime<
543
629
  }
544
630
  return handleTelegramSettingsMenuCallbackAction(query.id, query.data, {
545
631
  isProactivePushEnabled: deps.isProactivePushEnabled,
632
+ areRichDraftPreviewsEnabled: deps.areRichDraftPreviewsEnabled,
546
633
  getVoiceReplyMode: deps.getVoiceReplyMode,
547
634
  isVoiceReplyModeConfigured: deps.isVoiceReplyModeConfigured,
548
635
  getTimeInjectionMode: deps.getTimeInjectionMode,
549
636
  setProactivePushEnabled: deps.setProactivePushEnabled,
637
+ setRichDraftPreviewsEnabled: deps.setRichDraftPreviewsEnabled,
550
638
  setVoiceReplyMode: deps.setVoiceReplyMode,
551
639
  setTimeInjectionMode: deps.setTimeInjectionMode,
552
640
  updateSettingsMessage: (text, replyMarkup) =>
package/lib/routing.ts CHANGED
@@ -217,21 +217,13 @@ function formatTelegramAllTabMenuChooserText(command: string): string {
217
217
  function buildTelegramUnboundRerouteChooserMarkup(
218
218
  rerouteId: string,
219
219
  records: readonly Threads.TelegramTopicTargetRecord[],
220
- options: {
220
+ _options: {
221
221
  currentLeaderProfileKey?: string;
222
222
  currentInstanceId?: string;
223
223
  } = {},
224
224
  ): Menu.TelegramReplyMarkup {
225
225
  const activeRecords = records.filter((record) => record.status === "active");
226
- const canRestoreCurrentLeader = activeRecords.some(
227
- (record) =>
228
- typeof record.rerouteConfirmedAtMs === "number" &&
229
- isCurrentLeaderTopicRecord(
230
- record,
231
- options.currentLeaderProfileKey,
232
- options.currentInstanceId,
233
- ),
234
- );
226
+ const canRestoreAnyLiveThread = activeRecords.length > 0;
235
227
  const rows = activeRecords.map((record) => [
236
228
  {
237
229
  text: getTelegramRouteThreadButtonLabel(record),
@@ -242,7 +234,7 @@ function buildTelegramUnboundRerouteChooserMarkup(
242
234
  },
243
235
  ]);
244
236
  return {
245
- inline_keyboard: canRestoreCurrentLeader
237
+ inline_keyboard: canRestoreAnyLiveThread
246
238
  ? [
247
239
  ...rows,
248
240
  [
@@ -414,6 +406,7 @@ export interface TelegramInboundRouteRuntimeDeps<
414
406
  getCurrentInstanceId?: () => string | undefined;
415
407
  getMessageOwnership?: Updates.TelegramMessageOwnershipLookup;
416
408
  getTargetOwnership?: Updates.TelegramTargetOwnershipLookup;
409
+ recordMessageOwnership?: Updates.TelegramMessageOwnershipRecorder;
417
410
  getLiveThreadTargets?: () => Queue.TelegramQueueTarget[];
418
411
  getLocalThreadLabelForTarget?: (
419
412
  target: Queue.TelegramQueueTarget,
@@ -1861,6 +1854,7 @@ export function createTelegramInboundRouteRuntime<
1861
1854
  getCurrentInstanceId: deps.getCurrentInstanceId,
1862
1855
  getMessageOwnership: deps.getMessageOwnership,
1863
1856
  getTargetOwnership: deps.getTargetOwnership,
1857
+ recordMessageOwnership: deps.recordMessageOwnership,
1864
1858
  handleTelegramTopicLifecycleUpdate,
1865
1859
  foreignOwnedUpdateForwarder: deps.foreignOwnedUpdateForwarder,
1866
1860
  setAllowedUserId: deps.configStore.setAllowedUserId,
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 = 1500;
7
+ const TELEGRAM_TYPING_ACTION_INTERVAL_MS = 2500;
8
8
  const TELEGRAM_TYPING_IDLE_DRAIN_MAX_MS = 250;
9
9
 
10
10
  export interface TelegramRuntimeQueueCounters {
@@ -398,13 +398,9 @@ export function createTelegramTypingLoopStarter<TContext>(
398
398
  chatId: chatId ?? deps.getDefaultChatId(),
399
399
  target: options?.target,
400
400
  intervalMs: deps.intervalMs ?? TELEGRAM_TYPING_ACTION_INTERVAL_MS,
401
- sendTypingAction: async (targetChatId) => {
401
+ sendTypingAction: async (targetChatId, actionOptions) => {
402
402
  try {
403
- const threadParams = getTelegramTypingLoopThreadParams(options?.target);
404
- await deps.sendTypingAction(targetChatId, threadParams);
405
- if (threadParams?.message_thread_id !== undefined) {
406
- await deps.sendAggregateTypingAction?.(targetChatId);
407
- }
403
+ await deps.sendTypingAction(targetChatId, actionOptions);
408
404
  } catch (error) {
409
405
  const message =
410
406
  error instanceof Error ? error.message : String(error);
@@ -419,6 +415,26 @@ export function createTelegramTypingLoopStarter<TContext>(
419
415
  });
420
416
  }
421
417
  },
418
+ sendAggregateTypingAction: deps.sendAggregateTypingAction
419
+ ? async (targetChatId) => {
420
+ try {
421
+ await deps.sendAggregateTypingAction?.(targetChatId);
422
+ } catch (error) {
423
+ const message =
424
+ error instanceof Error ? error.message : String(error);
425
+ updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
426
+ error: message,
427
+ category: "typing",
428
+ phase: "status-update",
429
+ recordRuntimeEvent: deps.recordRuntimeEvent,
430
+ });
431
+ deps.recordRuntimeEvent?.("typing", error, {
432
+ chatId: targetChatId,
433
+ aggregate: true,
434
+ });
435
+ }
436
+ }
437
+ : undefined,
422
438
  });
423
439
  };
424
440
  }
package/lib/status.ts CHANGED
@@ -771,8 +771,6 @@ export function buildTelegramStatusBarText(
771
771
  return `${label} ${theme.fg("warning", "electing")}${queued}`;
772
772
  if (!state.pollingActive && state.busRole !== "follower")
773
773
  return `${label} ${theme.fg("muted", "disconnected")}${queued}`;
774
- if (state.busRole === "follower")
775
- return `${label} ${theme.fg("success", "follower")}${queued}`;
776
774
  if (state.compactionInProgress) {
777
775
  return `${label} ${theme.fg("warning", "compacting")}${queued}`;
778
776
  }
@@ -784,6 +782,8 @@ export function buildTelegramStatusBarText(
784
782
  processingStatus === "active" ? "warning" : "accent";
785
783
  return `${label} ${theme.fg(processingToken, processingStatus)}${queued}`;
786
784
  }
785
+ if (state.busRole === "follower")
786
+ return `${label} ${theme.fg("success", "follower")}${queued}`;
787
787
  if (state.busRole === "leader")
788
788
  return `${label} ${theme.fg("success", "leader")}`;
789
789
  return `${label} ${theme.fg("success", "connected")}`;
package/lib/threads.ts CHANGED
@@ -1438,7 +1438,7 @@ function getTelegramThreadNameEntropyIndex(
1438
1438
 
1439
1439
  export function getTelegramTopicThreadNameValidationError(
1440
1440
  threadName: string,
1441
- slot: string | undefined,
1441
+ _slot: string | undefined,
1442
1442
  ): string | undefined {
1443
1443
  const identity = getTelegramTopicIdentityName(threadName);
1444
1444
  const reasons: string[] = [];
@@ -1501,6 +1501,60 @@ function asInteger(value: unknown): number | undefined {
1501
1501
  return Number.isInteger(parsed) ? parsed : undefined;
1502
1502
  }
1503
1503
 
1504
+ export interface TelegramPromoteFollowerBindingToLeaderDeps {
1505
+ store: TelegramTopicTargetStore;
1506
+ instanceId: string;
1507
+ cwd?: string;
1508
+ target?: TelegramTarget;
1509
+ slot?: string;
1510
+ threadName?: string;
1511
+ nowMs?: number;
1512
+ }
1513
+
1514
+ export async function promoteTelegramFollowerBindingToLeader(
1515
+ deps: TelegramPromoteFollowerBindingToLeaderDeps,
1516
+ ): Promise<TelegramTopicTargetRecord | undefined> {
1517
+ const target = deps.target;
1518
+ if (typeof target?.threadId !== "number") return undefined;
1519
+ await deps.store.load();
1520
+ const nowMs = deps.nowMs ?? Date.now();
1521
+ const existing = deps.store
1522
+ .list()
1523
+ .find(
1524
+ (record) =>
1525
+ record.target.chatId === target.chatId &&
1526
+ record.target.threadId === target.threadId,
1527
+ );
1528
+ const owner: TelegramThreadOwner = {
1529
+ kind: "leader",
1530
+ cwd: deps.cwd,
1531
+ instanceId: deps.instanceId,
1532
+ };
1533
+ const record = deps.store.upsert({
1534
+ profileKey: getTelegramThreadOwnerKey(owner),
1535
+ owner,
1536
+ target: { chatId: target.chatId, threadId: target.threadId },
1537
+ status: "active",
1538
+ createdAtMs: existing?.createdAtMs ?? nowMs,
1539
+ updatedAtMs: nowMs,
1540
+ ...(existing?.threadName ?? deps.threadName
1541
+ ? { threadName: existing?.threadName ?? deps.threadName }
1542
+ : {}),
1543
+ instanceId: deps.instanceId,
1544
+ ...(existing?.slot ?? deps.slot ? { slot: existing?.slot ?? deps.slot } : {}),
1545
+ ...(existing?.syncStatus ? { syncStatus: existing.syncStatus } : {}),
1546
+ ...(existing?.lastSyncObservedAtMs !== undefined
1547
+ ? { lastSyncObservedAtMs: existing.lastSyncObservedAtMs }
1548
+ : {}),
1549
+ lastReconcileAction: "follower-promoted-to-leader",
1550
+ ...(existing?.rerouteConfirmedAtMs !== undefined
1551
+ ? { rerouteConfirmedAtMs: existing.rerouteConfirmedAtMs }
1552
+ : {}),
1553
+ });
1554
+ await deps.store.persist();
1555
+ return record;
1556
+ }
1557
+
1504
1558
  export interface TelegramOwnTopicProvisionDeps {
1505
1559
  getAllowedUserId: () => number | undefined;
1506
1560
  instanceId: string;
@@ -2077,7 +2131,7 @@ export function createTelegramTopicTargetProvisioner(
2077
2131
  existing.threadName ?? identityThreadName ?? bakedThreadName,
2078
2132
  instanceId: request.instanceId,
2079
2133
  slot,
2080
- owner: existing.owner ?? request.owner,
2134
+ owner: request.owner ?? existing.owner,
2081
2135
  lastError: undefined,
2082
2136
  });
2083
2137
  return { target: record.target, reused: true, record };
package/lib/updates.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  createTelegramThreadTarget,
10
10
  type TelegramTarget,
11
11
  } from "./target.ts";
12
+ import type { TelegramMessageOwnershipStore } from "./ownership.ts";
12
13
  import {
13
14
  createTelegramUserPairingRuntime,
14
15
  getTelegramAuthorizationState,
@@ -538,6 +539,14 @@ export function buildTelegramUpdateExecutionPlanFromUpdate<
538
539
 
539
540
  // --- Runtime ---
540
541
 
542
+ export type TelegramMessageOwnershipRecorderInput = Parameters<
543
+ TelegramMessageOwnershipStore["record"]
544
+ >[0];
545
+
546
+ export type TelegramMessageOwnershipRecorder = (
547
+ input: TelegramMessageOwnershipRecorderInput,
548
+ ) => void;
549
+
541
550
  export interface TelegramUpdateRuntimeDeps<
542
551
  TContext = unknown,
543
552
  TReactionUpdate extends TelegramMessageReactionUpdated =
@@ -549,6 +558,7 @@ export interface TelegramUpdateRuntimeDeps<
549
558
  getCurrentInstanceId?: () => string | undefined;
550
559
  getMessageOwnership?: TelegramMessageOwnershipLookup;
551
560
  getTargetOwnership?: TelegramTargetOwnershipLookup;
561
+ recordMessageOwnership?: TelegramMessageOwnershipRecorder;
552
562
  foreignOwnedUpdateForwarder?: TelegramForeignOwnedUpdateForwarder<
553
563
  TContext,
554
564
  TReactionUpdate,
@@ -612,6 +622,7 @@ export interface TelegramUpdateRuntimeControllerDeps<
612
622
  getCurrentInstanceId?: () => string | undefined;
613
623
  getMessageOwnership?: TelegramMessageOwnershipLookup;
614
624
  getTargetOwnership?: TelegramTargetOwnershipLookup;
625
+ recordMessageOwnership?: TelegramMessageOwnershipRecorder;
615
626
  foreignOwnedUpdateForwarder?: TelegramForeignOwnedUpdateForwarder<
616
627
  TContext,
617
628
  TelegramMessageReactionUpdated,
@@ -824,6 +835,7 @@ export function createTelegramPairedUpdateRuntime<
824
835
  getCurrentInstanceId: deps.getCurrentInstanceId,
825
836
  getMessageOwnership: deps.getMessageOwnership,
826
837
  getTargetOwnership: deps.getTargetOwnership,
838
+ recordMessageOwnership: deps.recordMessageOwnership,
827
839
  handleTelegramTopicLifecycleUpdate: deps.handleTelegramTopicLifecycleUpdate,
828
840
  foreignOwnedUpdateForwarder: deps.foreignOwnedUpdateForwarder,
829
841
  removePendingMediaGroupMessages: deps.removePendingMediaGroupMessages,
@@ -890,6 +902,7 @@ export function createTelegramUpdateRuntime<
890
902
  getCurrentInstanceId: deps.getCurrentInstanceId,
891
903
  getMessageOwnership: deps.getMessageOwnership,
892
904
  getTargetOwnership: deps.getTargetOwnership,
905
+ recordMessageOwnership: deps.recordMessageOwnership,
893
906
  foreignOwnedUpdateForwarder: deps.foreignOwnedUpdateForwarder,
894
907
  removePendingMediaGroupMessages: deps.removePendingMediaGroupMessages,
895
908
  removeQueuedTelegramTurnsByMessageIds:
@@ -1097,7 +1110,7 @@ export async function executeTelegramUpdatePlan<
1097
1110
  if (plan.shouldDeny) {
1098
1111
  await deps.answerGuestQuery(
1099
1112
  plan.guestMessage.guest_query_id,
1100
- "Access denied.",
1113
+ "🚫 Access denied.",
1101
1114
  );
1102
1115
  return;
1103
1116
  }
@@ -1109,11 +1122,40 @@ export async function executeTelegramUpdatePlan<
1109
1122
  }
1110
1123
  return;
1111
1124
  }
1125
+ const foreignMessageOwnership = getForeignTelegramMessageOwnership(
1126
+ getTelegramMessageReplyTarget(plan.message),
1127
+ deps,
1128
+ );
1129
+ if (foreignMessageOwnership) {
1130
+ if (plan.kind === "edited-message") {
1131
+ await deps.foreignOwnedUpdateForwarder?.forwardEditedMessage?.({
1132
+ message: plan.message,
1133
+ ownership: foreignMessageOwnership,
1134
+ ctx: deps.ctx,
1135
+ });
1136
+ } else {
1137
+ await deps.foreignOwnedUpdateForwarder?.forwardMessage?.({
1138
+ message: plan.message,
1139
+ ownership: foreignMessageOwnership,
1140
+ ctx: deps.ctx,
1141
+ });
1142
+ }
1143
+ return;
1144
+ }
1145
+ const messageTarget = getTelegramMessageTarget(plan.message);
1112
1146
  const foreignTargetOwnership = getForeignTelegramTargetOwnership(
1113
- getTelegramMessageTarget(plan.message),
1147
+ messageTarget,
1114
1148
  deps,
1115
1149
  );
1116
1150
  if (foreignTargetOwnership) {
1151
+ if (typeof plan.message.message_id === "number") {
1152
+ deps.recordMessageOwnership?.({
1153
+ chatId: messageTarget!.chatId,
1154
+ messageId: plan.message.message_id,
1155
+ target: messageTarget,
1156
+ instanceId: foreignTargetOwnership.instanceId,
1157
+ });
1158
+ }
1117
1159
  if (plan.kind === "edited-message") {
1118
1160
  await deps.foreignOwnedUpdateForwarder?.forwardEditedMessage?.({
1119
1161
  message: plan.message,
@@ -1129,7 +1171,6 @@ export async function executeTelegramUpdatePlan<
1129
1171
  }
1130
1172
  return;
1131
1173
  }
1132
- const messageTarget = getTelegramMessageTarget(plan.message);
1133
1174
  if (
1134
1175
  plan.kind === "message" &&
1135
1176
  messageTarget?.threadId != null &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.18.5",
3
+ "version": "0.19.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -42,7 +42,7 @@
42
42
  "BACKLOG.md",
43
43
  "CHANGELOG.md",
44
44
  "docs/",
45
- "screenshot.png"
45
+ "banner.png"
46
46
  ],
47
47
  "exports": {
48
48
  ".": "./index.ts",
File without changes