@llblab/pi-telegram 0.21.1 → 0.22.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/AGENTS.md +7 -4
- package/BACKLOG.md +29 -1
- package/CHANGELOG.md +479 -452
- package/docs/architecture.md +12 -10
- package/docs/delivery.md +2 -1
- package/docs/locks.md +13 -5
- package/docs/multi-instance-bus.md +5 -5
- package/index.ts +311 -633
- package/lib/bindings.ts +59 -21
- package/lib/bus-api.ts +12 -2
- package/lib/bus-follower.ts +327 -83
- package/lib/bus-leader.ts +201 -121
- package/lib/bus.ts +345 -37
- package/lib/config.ts +168 -28
- package/lib/delivery.ts +148 -61
- package/lib/lifecycle.ts +199 -8
- package/lib/locks.ts +854 -57
- package/lib/logs.ts +185 -22
- package/lib/media.ts +79 -24
- package/lib/model.ts +5 -10
- package/lib/ownership.ts +150 -6
- package/lib/polling.ts +156 -7
- package/lib/preview.ts +15 -3
- package/lib/queue.ts +167 -20
- package/lib/routing.ts +68 -12
- package/lib/sync.ts +105 -32
- package/lib/telegram-api.ts +86 -10
- package/lib/text-groups.ts +71 -15
- package/lib/thread-reconciler.ts +49 -36
- package/lib/threads.ts +505 -118
- package/package.json +1 -1
package/lib/bindings.ts
CHANGED
|
@@ -38,12 +38,15 @@ type TelegramBridgeStatusUpdater =
|
|
|
38
38
|
interface TelegramCommandsAndToolsBindingDeps {
|
|
39
39
|
pi: Pi.ExtensionAPI;
|
|
40
40
|
configStore: Config.TelegramConfigStore;
|
|
41
|
-
persistConfig: () => Promise<void>;
|
|
41
|
+
persistConfig: (config?: Config.TelegramConfig) => Promise<void>;
|
|
42
42
|
setup: Setup.TelegramSetupGuard;
|
|
43
43
|
activeTurnRuntime: Queue.TelegramActiveTurnStore<Queue.PendingTelegramTurn>;
|
|
44
44
|
lockedPollingRuntime: Locks.TelegramLockedPollingRuntime<Pi.ExtensionContext>;
|
|
45
45
|
stopPolling?: () => Promise<void | string>;
|
|
46
|
-
|
|
46
|
+
onTransportChanged?: () => Promise<void> | void;
|
|
47
|
+
getStatusLines: (
|
|
48
|
+
options?: Status.TelegramBridgeStatusLineOptions,
|
|
49
|
+
) => string[];
|
|
47
50
|
buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
|
|
48
51
|
sendMarkdownReply: (
|
|
49
52
|
chatId: number,
|
|
@@ -67,6 +70,7 @@ export function registerTelegramCommandsAndTools({
|
|
|
67
70
|
activeTurnRuntime,
|
|
68
71
|
lockedPollingRuntime,
|
|
69
72
|
stopPolling,
|
|
73
|
+
onTransportChanged,
|
|
70
74
|
getStatusLines,
|
|
71
75
|
buttonActionStore,
|
|
72
76
|
sendMarkdownReply,
|
|
@@ -113,6 +117,7 @@ export function registerTelegramCommandsAndTools({
|
|
|
113
117
|
await (stopPolling ?? lockedPollingRuntime.stop)();
|
|
114
118
|
}
|
|
115
119
|
configStore.activateProfile(undefined);
|
|
120
|
+
await onTransportChanged?.();
|
|
116
121
|
} else {
|
|
117
122
|
const storedConfig = configStore.getStoredConfig();
|
|
118
123
|
setupConfigStore = Config.createTelegramConfigStore({
|
|
@@ -129,28 +134,36 @@ export function registerTelegramCommandsAndTools({
|
|
|
129
134
|
setupConfigStore.activateProfile(profileName);
|
|
130
135
|
persistSetupConfig = async () => {
|
|
131
136
|
try {
|
|
137
|
+
if (previousProfileName !== profileName) {
|
|
138
|
+
await (stopPolling ?? lockedPollingRuntime.stop)();
|
|
139
|
+
}
|
|
140
|
+
const profile = Config.getTelegramProfileFields(
|
|
141
|
+
setupConfigStore.get(),
|
|
142
|
+
);
|
|
143
|
+
if (!profile) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Telegram profile "${profileName}" has no token.`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
await configStore.load();
|
|
149
|
+
const latestConfig = configStore.getStoredConfig();
|
|
132
150
|
configStore.activateProfile(undefined);
|
|
133
151
|
configStore.set({
|
|
134
|
-
...
|
|
152
|
+
...latestConfig,
|
|
135
153
|
profiles: {
|
|
136
|
-
...(
|
|
137
|
-
[profileName]:
|
|
138
|
-
botToken: "",
|
|
139
|
-
},
|
|
154
|
+
...(latestConfig.profiles ?? {}),
|
|
155
|
+
[profileName]: profile,
|
|
140
156
|
},
|
|
141
157
|
});
|
|
142
158
|
configStore.activateProfile(profileName);
|
|
143
|
-
|
|
144
|
-
await persistConfig();
|
|
159
|
+
await onTransportChanged?.();
|
|
160
|
+
await persistConfig(configStore.get());
|
|
145
161
|
} catch (error) {
|
|
146
|
-
configStore.
|
|
147
|
-
configStore.set(storedConfig);
|
|
162
|
+
await configStore.load().catch(() => undefined);
|
|
148
163
|
configStore.activateProfile(previousProfileName);
|
|
164
|
+
await onTransportChanged?.();
|
|
149
165
|
throw error;
|
|
150
166
|
}
|
|
151
|
-
if (previousProfileName !== profileName) {
|
|
152
|
-
await (stopPolling ?? lockedPollingRuntime.stop)();
|
|
153
|
-
}
|
|
154
167
|
};
|
|
155
168
|
}
|
|
156
169
|
const runSetup = Setup.createTelegramSetupPromptRuntime({
|
|
@@ -177,23 +190,25 @@ export function registerTelegramCommandsAndTools({
|
|
|
177
190
|
getProfileNames: () =>
|
|
178
191
|
Config.getTelegramProfileNames(configStore.getStoredConfig()),
|
|
179
192
|
activateDefaultProfileConfig: async () => {
|
|
180
|
-
await configStore.load();
|
|
181
193
|
const previousProfileName = configStore.getActiveProfileName();
|
|
194
|
+
await configStore.load();
|
|
182
195
|
if (previousProfileName) {
|
|
183
196
|
await (stopPolling ?? lockedPollingRuntime.stop)();
|
|
184
197
|
}
|
|
185
198
|
configStore.activateProfile(undefined);
|
|
199
|
+
await onTransportChanged?.();
|
|
186
200
|
},
|
|
187
201
|
activateProfileConfig: async (_ctx, profileName) => {
|
|
202
|
+
const previousProfileName = configStore.getActiveProfileName();
|
|
188
203
|
await configStore.load();
|
|
189
204
|
if (!Config.isValidTelegramProfileName(profileName)) return false;
|
|
190
205
|
const storedConfig = configStore.getStoredConfig();
|
|
191
206
|
if (!storedConfig.profiles?.[profileName]) return false;
|
|
192
|
-
const previousProfileName = configStore.getActiveProfileName();
|
|
193
207
|
if (previousProfileName !== profileName) {
|
|
194
208
|
await (stopPolling ?? lockedPollingRuntime.stop)();
|
|
195
209
|
}
|
|
196
210
|
if (!configStore.activateProfile(profileName)) return false;
|
|
211
|
+
await onTransportChanged?.();
|
|
197
212
|
return true;
|
|
198
213
|
},
|
|
199
214
|
});
|
|
@@ -272,6 +287,8 @@ interface TelegramLifecycleBindingDeps {
|
|
|
272
287
|
isProactivePushEnabled: () => boolean;
|
|
273
288
|
canSendProactivePush: (ctx: Pi.ExtensionContext) => boolean;
|
|
274
289
|
canSendAgentActivity: (ctx: Pi.ExtensionContext) => boolean;
|
|
290
|
+
isSessionContextActive: (ctx: Pi.ExtensionContext) => boolean;
|
|
291
|
+
isTurnTransportActive?: (turn: Queue.PendingTelegramTurn) => boolean;
|
|
275
292
|
updateStatus: TelegramBridgeStatusUpdater;
|
|
276
293
|
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
277
294
|
}
|
|
@@ -307,6 +324,8 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
307
324
|
isProactivePushEnabled,
|
|
308
325
|
canSendProactivePush,
|
|
309
326
|
canSendAgentActivity,
|
|
327
|
+
isSessionContextActive = () => true,
|
|
328
|
+
isTurnTransportActive,
|
|
310
329
|
updateStatus,
|
|
311
330
|
recordRuntimeEvent,
|
|
312
331
|
}: TelegramLifecycleBindingDeps): void {
|
|
@@ -332,7 +351,9 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
332
351
|
const stagingTarget = proactivePushTargetGetter();
|
|
333
352
|
const stagingChatId = stagingTarget?.chatId ?? proactivePushChatIdGetter();
|
|
334
353
|
if (stagingChatId === undefined) {
|
|
335
|
-
throw new Error(
|
|
354
|
+
throw new Error(
|
|
355
|
+
"Guest attachment staging requires a paired Telegram chat",
|
|
356
|
+
);
|
|
336
357
|
}
|
|
337
358
|
await OutboundAttachments.deliverTelegramGuestCachedAttachment({
|
|
338
359
|
guestQueryId: turn.guestQueryId!,
|
|
@@ -346,7 +367,8 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
346
367
|
answerGuestText: (guestQueryId, text) =>
|
|
347
368
|
answerGuestQuery(guestQueryId, text),
|
|
348
369
|
fallbackText:
|
|
349
|
-
caption ||
|
|
370
|
+
caption ||
|
|
371
|
+
"Telegram bridge could not deliver the requested attachment.",
|
|
350
372
|
deleteMessage,
|
|
351
373
|
recordRuntimeEvent,
|
|
352
374
|
});
|
|
@@ -455,6 +477,8 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
455
477
|
getFoldQueuedPromptsIntoHistory:
|
|
456
478
|
lifecycle.shouldFoldQueuedPromptsIntoHistory,
|
|
457
479
|
resetRuntimeState: agentEndResetter,
|
|
480
|
+
isSessionActive: isSessionContextActive,
|
|
481
|
+
isTurnTransportActive,
|
|
458
482
|
waitForTypingIdle: typing.waitForIdle,
|
|
459
483
|
dispatchNextQueuedTelegramTurn,
|
|
460
484
|
requestDeferredDispatchNextQueuedTelegramTurn:
|
|
@@ -512,6 +536,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
512
536
|
});
|
|
513
537
|
};
|
|
514
538
|
const compactionObserver = Lifecycle.createTelegramCompactionObserverRuntime({
|
|
539
|
+
isContextActive: isSessionContextActive,
|
|
515
540
|
setCompactionInProgress: lifecycle.setCompactionInProgress,
|
|
516
541
|
updateStatus,
|
|
517
542
|
startTypingLoop: startAgentActivityTypingLoop,
|
|
@@ -532,34 +557,41 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
532
557
|
});
|
|
533
558
|
const messageActivityHooks = messageActivityTypingHooks;
|
|
534
559
|
Lifecycle.registerTelegramLifecycleHooks(pi, {
|
|
560
|
+
isSessionActive: isSessionContextActive,
|
|
535
561
|
...sessionLifecycleRuntime,
|
|
536
562
|
...agentLifecycleHooks,
|
|
537
563
|
onInput(event) {
|
|
538
564
|
activityRuntime.recordInputSource(event.source ?? "unknown");
|
|
539
565
|
},
|
|
540
566
|
async onSessionStart(event, ctx) {
|
|
567
|
+
previewRuntime.invalidate();
|
|
541
568
|
activityRuntime.onSessionStart?.();
|
|
542
569
|
await sessionLifecycleRuntime.onSessionStart(event, ctx);
|
|
543
570
|
},
|
|
544
571
|
async onSessionShutdown(event, ctx) {
|
|
572
|
+
if (!isSessionContextActive(ctx)) return;
|
|
545
573
|
activityRuntime.onSessionShutdown();
|
|
546
574
|
compactionObserver.onSessionShutdown();
|
|
547
575
|
await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
|
|
548
576
|
},
|
|
549
577
|
onSessionBeforeCompact(event, ctx) {
|
|
578
|
+
if (!isSessionContextActive(ctx)) return;
|
|
550
579
|
activityRuntime.onCompactionStart(Pi.getSessionCompactionReason(event));
|
|
551
580
|
compactionObserver.onSessionBeforeCompact(event, ctx);
|
|
552
581
|
},
|
|
553
582
|
onSessionCompact(event, ctx) {
|
|
583
|
+
if (!isSessionContextActive(ctx)) return;
|
|
554
584
|
activityRuntime.onCompactionEnd(Pi.getSessionCompactionReason(event));
|
|
555
585
|
compactionObserver.onSessionCompact(event, ctx);
|
|
556
586
|
},
|
|
557
587
|
async onAgentStart(event, ctx) {
|
|
588
|
+
if (!isSessionContextActive(ctx)) return;
|
|
558
589
|
await agentStartWithDedupReset(event, ctx);
|
|
559
590
|
activityRuntime.onAgentStart(activeTurnRuntime.get()?.target);
|
|
560
591
|
startAgentActivityTypingLoop(ctx);
|
|
561
592
|
},
|
|
562
|
-
async onToolExecutionStart(event,
|
|
593
|
+
async onToolExecutionStart(event, ctx) {
|
|
594
|
+
if (!isSessionContextActive(ctx)) return;
|
|
563
595
|
agentLifecycleHooks.onToolExecutionStart();
|
|
564
596
|
activityRuntime.onToolStart({
|
|
565
597
|
toolCallId: event.toolCallId,
|
|
@@ -567,7 +599,8 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
567
599
|
args: event.args,
|
|
568
600
|
});
|
|
569
601
|
},
|
|
570
|
-
onToolExecutionUpdate(event) {
|
|
602
|
+
onToolExecutionUpdate(event, ctx) {
|
|
603
|
+
if (!isSessionContextActive(ctx)) return;
|
|
571
604
|
activityRuntime.onToolUpdate({
|
|
572
605
|
toolCallId: event.toolCallId,
|
|
573
606
|
toolName: event.toolName,
|
|
@@ -575,6 +608,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
575
608
|
});
|
|
576
609
|
},
|
|
577
610
|
async onToolExecutionEnd(event, ctx) {
|
|
611
|
+
if (!isSessionContextActive(ctx)) return;
|
|
578
612
|
activityRuntime.onToolEnd({
|
|
579
613
|
toolCallId: event.toolCallId,
|
|
580
614
|
toolName: event.toolName,
|
|
@@ -584,9 +618,11 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
584
618
|
agentLifecycleHooks.onToolExecutionEnd(event, ctx);
|
|
585
619
|
},
|
|
586
620
|
async onMessageStart(event, ctx) {
|
|
621
|
+
if (!isSessionContextActive(ctx)) return;
|
|
587
622
|
await messageActivityHooks.onMessageStart(event, ctx);
|
|
588
623
|
},
|
|
589
624
|
async onMessageUpdate(event, ctx) {
|
|
625
|
+
if (!isSessionContextActive(ctx)) return;
|
|
590
626
|
if (event.assistantMessageEvent) {
|
|
591
627
|
activityRuntime.onAssistantEvent(
|
|
592
628
|
event.assistantMessageEvent as Activity.TelegramAssistantStreamEvent,
|
|
@@ -595,10 +631,12 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
595
631
|
await messageActivityHooks.onMessageUpdate(event, ctx);
|
|
596
632
|
},
|
|
597
633
|
async onAgentEnd(event, ctx) {
|
|
634
|
+
if (!isSessionContextActive(ctx)) return;
|
|
598
635
|
activityRuntime.onAgentEnd();
|
|
599
636
|
await agentLifecycleHooks.onAgentEnd(event, ctx);
|
|
600
637
|
},
|
|
601
|
-
onAgentSettled() {
|
|
638
|
+
onAgentSettled(_event, ctx) {
|
|
639
|
+
if (!isSessionContextActive(ctx)) return;
|
|
602
640
|
activityRuntime.onAgentSettled();
|
|
603
641
|
},
|
|
604
642
|
onBeforeAgentStart: Prompts.createTelegramProactiveBeforeAgentStartHook({
|
package/lib/bus-api.ts
CHANGED
|
@@ -55,6 +55,12 @@ function withDefaultThreadTarget(
|
|
|
55
55
|
: body;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
function rejectTelegramDirectOwnership(method: string): Promise<never> {
|
|
59
|
+
return Promise.reject(
|
|
60
|
+
new Error(`Telegram ${method} requires direct transport ownership.`),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
58
64
|
export function createTelegramAggregateTypingActionSender(
|
|
59
65
|
runtime: Pick<TelegramBridgeApiRuntime, "call">,
|
|
60
66
|
): (chatId: number) => Promise<unknown> {
|
|
@@ -114,13 +120,17 @@ export function createTelegramBusAwareApiRuntime(
|
|
|
114
120
|
]) as Promise<string>);
|
|
115
121
|
},
|
|
116
122
|
deleteWebhook(signal?: AbortSignal): Promise<boolean> {
|
|
117
|
-
return deps.
|
|
123
|
+
return deps.ownsDirect()
|
|
124
|
+
? deps.directRuntime.deleteWebhook(signal)
|
|
125
|
+
: rejectTelegramDirectOwnership("deleteWebhook");
|
|
118
126
|
},
|
|
119
127
|
getUpdates(
|
|
120
128
|
body: Record<string, unknown>,
|
|
121
129
|
signal?: AbortSignal,
|
|
122
130
|
): Promise<TelegramUpdate[]> {
|
|
123
|
-
return deps.
|
|
131
|
+
return deps.ownsDirect()
|
|
132
|
+
? deps.directRuntime.getUpdates(body, signal)
|
|
133
|
+
: rejectTelegramDirectOwnership("getUpdates");
|
|
124
134
|
},
|
|
125
135
|
setMyCommands(commands): Promise<boolean> {
|
|
126
136
|
return deps.ownsDirect()
|