@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.
- package/AGENTS.md +5 -4
- package/BACKLOG.md +5 -10
- package/CHANGELOG.md +23 -0
- package/README.md +176 -226
- package/docs/architecture.md +3 -3
- package/docs/multi-instance-bus.md +23 -2
- package/index.ts +35 -3
- package/lib/bindings.ts +3 -14
- package/lib/bus-follower.ts +26 -7
- package/lib/bus-leader.ts +152 -27
- package/lib/bus.ts +44 -0
- package/lib/config.ts +22 -0
- package/lib/menu-settings.ts +88 -0
- package/lib/routing.ts +5 -11
- package/lib/runtime.ts +23 -7
- package/lib/status.ts +2 -2
- package/lib/threads.ts +56 -2
- package/lib/updates.ts +44 -3
- package/package.json +2 -2
- /package/{screenshot.png → banner.png} +0 -0
package/lib/menu-settings.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
45
|
+
"banner.png"
|
|
46
46
|
],
|
|
47
47
|
"exports": {
|
|
48
48
|
".": "./index.ts",
|
|
File without changes
|