@llblab/pi-telegram 0.20.6 → 0.21.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
@@ -4,6 +4,7 @@
4
4
  * Owns pi-facing tool, command, and lifecycle hook registration for the entrypoint
5
5
  */
6
6
 
7
+ import * as Activity from "./activity.ts";
7
8
  import * as CommandTemplates from "./command-templates.ts";
8
9
  import * as Commands from "./commands.ts";
9
10
  import * as Config from "./config.ts";
@@ -200,6 +201,7 @@ export function registerTelegramCommandsAndTools({
200
201
 
201
202
  interface TelegramLifecycleBindingDeps {
202
203
  pi: Pi.ExtensionAPI;
204
+ activityRuntime: Activity.TelegramActivityRuntime;
203
205
  sessionLifecycleRuntime: Pick<
204
206
  Lifecycle.TelegramLifecycleRegistrationDeps,
205
207
  "onSessionStart" | "onSessionShutdown" | "onModelSelect"
@@ -276,6 +278,7 @@ interface TelegramLifecycleBindingDeps {
276
278
 
277
279
  export function registerTelegramLifecycleRuntimeHooks({
278
280
  pi,
281
+ activityRuntime,
279
282
  sessionLifecycleRuntime,
280
283
  configStore,
281
284
  abort,
@@ -517,6 +520,7 @@ export function registerTelegramLifecycleRuntimeHooks({
517
520
  deferredQueueDispatchRuntime.request,
518
521
  dispatchNextQueuedTelegramTurn,
519
522
  recordRuntimeEvent,
523
+ onCompactionAbandoned: activityRuntime.onCompactionAbandoned,
520
524
  });
521
525
  const messageActivityTypingHooks =
522
526
  Lifecycle.createTelegramMessageActivityTypingHooks({
@@ -530,29 +534,77 @@ export function registerTelegramLifecycleRuntimeHooks({
530
534
  Lifecycle.registerTelegramLifecycleHooks(pi, {
531
535
  ...sessionLifecycleRuntime,
532
536
  ...agentLifecycleHooks,
537
+ onInput(event) {
538
+ activityRuntime.recordInputSource(event.source ?? "unknown");
539
+ },
540
+ async onSessionStart(event, ctx) {
541
+ activityRuntime.onSessionStart?.();
542
+ await sessionLifecycleRuntime.onSessionStart(event, ctx);
543
+ },
533
544
  async onSessionShutdown(event, ctx) {
545
+ activityRuntime.onSessionShutdown();
534
546
  compactionObserver.onSessionShutdown();
535
547
  await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
536
548
  },
537
- onSessionBeforeCompact: compactionObserver.onSessionBeforeCompact,
538
- onSessionCompact: compactionObserver.onSessionCompact,
549
+ onSessionBeforeCompact(event, ctx) {
550
+ activityRuntime.onCompactionStart(Pi.getSessionCompactionReason(event));
551
+ compactionObserver.onSessionBeforeCompact(event, ctx);
552
+ },
553
+ onSessionCompact(event, ctx) {
554
+ activityRuntime.onCompactionEnd(Pi.getSessionCompactionReason(event));
555
+ compactionObserver.onSessionCompact(event, ctx);
556
+ },
539
557
  async onAgentStart(event, ctx) {
540
558
  await agentStartWithDedupReset(event, ctx);
559
+ activityRuntime.onAgentStart(activeTurnRuntime.get()?.target);
541
560
  startAgentActivityTypingLoop(ctx);
542
561
  },
543
- async onToolExecutionStart(_event, _ctx) {
562
+ async onToolExecutionStart(event, _ctx) {
544
563
  agentLifecycleHooks.onToolExecutionStart();
564
+ activityRuntime.onToolStart({
565
+ toolCallId: event.toolCallId,
566
+ toolName: event.toolName,
567
+ args: event.args,
568
+ });
569
+ },
570
+ onToolExecutionUpdate(event) {
571
+ activityRuntime.onToolUpdate({
572
+ toolCallId: event.toolCallId,
573
+ toolName: event.toolName,
574
+ update: event.partialResult,
575
+ });
576
+ },
577
+ async onToolExecutionEnd(event, ctx) {
578
+ activityRuntime.onToolEnd({
579
+ toolCallId: event.toolCallId,
580
+ toolName: event.toolName,
581
+ result: event.result,
582
+ isError: event.isError,
583
+ });
584
+ agentLifecycleHooks.onToolExecutionEnd(event, ctx);
585
+ },
586
+ async onMessageStart(event, ctx) {
587
+ await messageActivityHooks.onMessageStart(event, ctx);
588
+ },
589
+ async onMessageUpdate(event, ctx) {
590
+ if (event.assistantMessageEvent) {
591
+ activityRuntime.onAssistantEvent(
592
+ event.assistantMessageEvent as Activity.TelegramAssistantStreamEvent,
593
+ );
594
+ }
595
+ await messageActivityHooks.onMessageUpdate(event, ctx);
596
+ },
597
+ async onAgentEnd(event, ctx) {
598
+ activityRuntime.onAgentEnd();
599
+ await agentLifecycleHooks.onAgentEnd(event, ctx);
545
600
  },
546
- onToolExecutionUpdate() {},
547
- async onToolExecutionEnd(_event, ctx) {
548
- agentLifecycleHooks.onToolExecutionEnd(_event, ctx);
601
+ onAgentSettled() {
602
+ activityRuntime.onAgentSettled();
549
603
  },
550
- onAgentEnd: agentLifecycleHooks.onAgentEnd,
551
604
  onBeforeAgentStart: Prompts.createTelegramProactiveBeforeAgentStartHook({
552
605
  isConfigured: configStore.hasBotToken,
553
606
  isProactivePushEnabled,
554
607
  isCurrentOwner: lockOwnershipGuard.ownsContext,
555
608
  }),
556
- ...messageActivityHooks,
557
609
  });
558
610
  }
package/lib/bus-api.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * Wraps the direct Telegram Bot API runtime so follower instances can route outbound calls through the bus leader
5
5
  */
6
6
 
7
+ import { stripTelegramBusApiMetadata } from "./bus.ts";
7
8
  import type {
8
9
  TelegramAnswerGuestQueryOptions,
9
10
  TelegramApiCallOptions,
@@ -224,7 +225,7 @@ export function createTelegramBusAwareApiRuntime(
224
225
  },
225
226
  sendMessage(body: TelegramSendMessageBody): Promise<TelegramSentMessage> {
226
227
  return deps.ownsDirect()
227
- ? deps.directRuntime.sendMessage(body)
228
+ ? deps.directRuntime.sendMessage(stripTelegramBusApiMetadata(body))
228
229
  : deps
229
230
  .callFollowerApi("call", ["sendMessage", body])
230
231
  .then(asSentMessage);
package/lib/bus-leader.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  isTelegramBusEnvelopeAuthorized,
17
17
  getTelegramBusFollowerSocketPath,
18
18
  sendTelegramBusLocalEnvelope,
19
+ stripTelegramBusApiMetadata,
19
20
  type TelegramBusEnvelope,
20
21
  type TelegramBusFollowerRegistry,
21
22
  type TelegramBusFollowerView,
@@ -795,7 +796,9 @@ export function createTelegramBusLeaderApiProxy(
795
796
  ): (method: string, args: unknown[]) => Promise<unknown> {
796
797
  return async (method, args) => {
797
798
  if (method === "call") {
798
- const body = args[1] as Record<string, unknown>;
799
+ const body = stripTelegramBusApiMetadata(
800
+ args[1] as Record<string, unknown>,
801
+ );
799
802
  try {
800
803
  return await deps.call(
801
804
  args[0] as string,
package/lib/bus.ts CHANGED
@@ -145,6 +145,38 @@ export function getTelegramFollowerTargetOwnership(input: {
145
145
  return record?.instanceId ? { instanceId: record.instanceId } : undefined;
146
146
  }
147
147
 
148
+ const TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD =
149
+ "__piTelegramAggregateDelivery";
150
+
151
+ export function markTelegramBusAggregateDelivery<T extends Record<string, unknown>>(
152
+ body: T,
153
+ ): T {
154
+ return {
155
+ ...body,
156
+ [TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD]: true,
157
+ };
158
+ }
159
+
160
+ export function isTelegramBusAggregateDelivery(body: unknown): boolean {
161
+ return Boolean(
162
+ body &&
163
+ typeof body === "object" &&
164
+ !Array.isArray(body) &&
165
+ (body as Record<string, unknown>)[
166
+ TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD
167
+ ] === true,
168
+ );
169
+ }
170
+
171
+ export function stripTelegramBusApiMetadata<T extends Record<string, unknown>>(
172
+ body: T,
173
+ ): T {
174
+ if (!(TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD in body)) return body;
175
+ const clean = { ...body };
176
+ delete clean[TELEGRAM_BUS_AGGREGATE_DELIVERY_FIELD];
177
+ return clean;
178
+ }
179
+
148
180
  export function isTelegramFollowerApiCallAllowed(input: {
149
181
  follower: TelegramBusFollowerView;
150
182
  method: string;
@@ -227,6 +259,12 @@ export function isTelegramFollowerApiCallAllowed(input: {
227
259
  return isBotCommandRegistration(input.args[1]);
228
260
  if (apiMethod === "sendChatAction")
229
261
  return isTargetChatScoped(input.args[1]);
262
+ if (apiMethod === "sendMessage" && isTelegramBusAggregateDelivery(input.args[1])) {
263
+ const body = input.args[1] as Record<string, unknown>;
264
+ return (
265
+ body.message_thread_id === undefined && isTargetChatScoped(body)
266
+ );
267
+ }
230
268
  if (apiMethod === "deleteMessage" || apiMethod === "editMessageText") {
231
269
  return isTargetMessageScoped(input.args[1]);
232
270
  }