@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/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
- getStatusLines: (options?: Status.TelegramBridgeStatusLineOptions) => string[];
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
- ...storedConfig,
152
+ ...latestConfig,
135
153
  profiles: {
136
- ...(storedConfig.profiles ?? {}),
137
- [profileName]: storedConfig.profiles?.[profileName] ?? {
138
- botToken: "",
139
- },
154
+ ...(latestConfig.profiles ?? {}),
155
+ [profileName]: profile,
140
156
  },
141
157
  });
142
158
  configStore.activateProfile(profileName);
143
- configStore.set(setupConfigStore.get());
144
- await persistConfig();
159
+ await onTransportChanged?.();
160
+ await persistConfig(configStore.get());
145
161
  } catch (error) {
146
- configStore.activateProfile(undefined);
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("Guest attachment staging requires a paired Telegram chat");
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 || "Telegram bridge could not deliver the requested attachment.",
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, _ctx) {
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.directRuntime.deleteWebhook(signal);
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.directRuntime.getUpdates(body, signal);
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()