@llblab/pi-kit 0.22.0 → 0.22.2

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/node_modules/@llblab/pi-telegram/AGENTS.md +4 -2
  4. package/node_modules/@llblab/pi-telegram/BACKLOG.md +3 -8
  5. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
  6. package/node_modules/@llblab/pi-telegram/README.md +3 -1
  7. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +1 -4
  8. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +0 -1
  9. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +0 -3
  10. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +22 -3
  11. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +1 -2
  12. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +3 -3
  13. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +17 -3
  14. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +5 -7
  15. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -5
  16. package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +12 -12
  17. package/node_modules/@llblab/pi-telegram/dist/lib/model.js +49 -35
  18. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +0 -2
  19. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +1 -8
  20. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +0 -1
  21. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +3 -6
  22. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +4 -55
  23. package/node_modules/@llblab/pi-telegram/dist/lib/skills.d.ts +2 -2
  24. package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +9 -2
  25. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +36 -10
  26. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  27. package/node_modules/@llblab/pi-telegram/docs/architecture.md +8 -8
  28. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +3 -3
  29. package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
  30. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +7 -4
  31. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +0 -7
  32. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +27 -3
  33. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1 -3
  34. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +25 -4
  35. package/node_modules/@llblab/pi-telegram/lib/menu.ts +14 -9
  36. package/node_modules/@llblab/pi-telegram/lib/model.ts +77 -44
  37. package/node_modules/@llblab/pi-telegram/lib/queue.ts +1 -8
  38. package/node_modules/@llblab/pi-telegram/lib/routing.ts +0 -1
  39. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +7 -58
  40. package/node_modules/@llblab/pi-telegram/lib/skills.ts +10 -2
  41. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +43 -17
  42. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  43. package/package.json +2 -2
@@ -13,6 +13,7 @@ import {
13
13
  resolveScopedModelPatterns,
14
14
  type ScopedTelegramModel,
15
15
  sortScopedModels,
16
+ type TelegramModelSwitchContinuationSource,
16
17
  type ThinkingLevel,
17
18
  } from "./model.ts";
18
19
 
@@ -157,9 +158,13 @@ export type TelegramModelMenuCallbackDeps<
157
158
  setModel: (model: TModel) => Promise<boolean>;
158
159
  setCurrentModel: (model: TModel) => void;
159
160
  setThinkingLevel: (level: ThinkingLevel) => void;
160
- stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel>) => void;
161
+ stagePendingModelSwitch: (
162
+ selection: ScopedTelegramModel<TModel>,
163
+ continuationTurn: TelegramModelSwitchContinuationSource,
164
+ ) => void;
161
165
  restartInterruptedTelegramTurn: (
162
166
  selection: ScopedTelegramModel<TModel>,
167
+ continuationTurn: TelegramModelSwitchContinuationSource,
163
168
  ) => Promise<boolean> | boolean;
164
169
  };
165
170
 
@@ -961,14 +966,28 @@ export async function handleTelegramModelMenuCallbackAction<
961
966
  if (plan.selection.thinkingLevel) {
962
967
  deps.setThinkingLevel(plan.selection.thinkingLevel);
963
968
  }
964
- await deps.updateModelMenuMessage();
969
+ const continuationTurn: TelegramModelSwitchContinuationSource = {
970
+ chatId: params.state.chatId,
971
+ replyToMessageId: params.state.messageId,
972
+ target: {
973
+ chatId: params.state.chatId,
974
+ ...(params.state.threadId === undefined
975
+ ? {}
976
+ : { threadId: params.state.threadId }),
977
+ },
978
+ };
965
979
  if (plan.mode === "restart-after-tool") {
966
- deps.stagePendingModelSwitch(plan.selection);
980
+ deps.stagePendingModelSwitch(plan.selection, continuationTurn);
981
+ await deps.updateModelMenuMessage();
967
982
  await deps.answerCallbackQuery(callbackQueryId, plan.callbackText);
968
983
  return true;
969
984
  }
970
985
  if (plan.mode === "restart-now") {
971
- const restarted = await deps.restartInterruptedTelegramTurn(plan.selection);
986
+ const restarted = await deps.restartInterruptedTelegramTurn(
987
+ plan.selection,
988
+ continuationTurn,
989
+ );
990
+ await deps.updateModelMenuMessage();
972
991
  if (!restarted) {
973
992
  await deps.answerCallbackQuery(
974
993
  callbackQueryId,
@@ -976,6 +995,8 @@ export async function handleTelegramModelMenuCallbackAction<
976
995
  );
977
996
  return true;
978
997
  }
998
+ } else {
999
+ await deps.updateModelMenuMessage();
979
1000
  }
980
1001
  await deps.answerCallbackQuery(callbackQueryId, plan.callbackText);
981
1002
  return true;
@@ -30,6 +30,7 @@ import {
30
30
  import {
31
31
  type MenuModel,
32
32
  type ScopedTelegramModel,
33
+ type TelegramModelSwitchContinuationSource,
33
34
  type ThinkingLevel,
34
35
  } from "./model.ts";
35
36
  import type { TelegramInputRichMessage } from "./telegram-api.ts";
@@ -194,7 +195,6 @@ export interface TelegramMenuCallbackRuntimeDeps<
194
195
  text?: string,
195
196
  ) => Promise<void>;
196
197
  isIdle: (ctx: TContext) => boolean;
197
- hasActiveTelegramTurn: () => boolean;
198
198
  hasAbortHandler: () => boolean;
199
199
  hasActiveToolExecutions: () => boolean;
200
200
  persistScopedModelPatterns?: (
@@ -206,10 +206,12 @@ export interface TelegramMenuCallbackRuntimeDeps<
206
206
  stagePendingModelSwitch: (
207
207
  selection: ScopedTelegramModel<TModel>,
208
208
  ctx: TContext,
209
+ continuationTurn: TelegramModelSwitchContinuationSource,
209
210
  ) => void;
210
211
  restartInterruptedTelegramTurn: (
211
212
  selection: ScopedTelegramModel<TModel>,
212
213
  ctx: TContext,
214
+ continuationTurn: TelegramModelSwitchContinuationSource,
213
215
  ) => Promise<boolean> | boolean;
214
216
  sectionRegistry?: TelegramSectionRegistry;
215
217
  editInteractiveMessage?: (
@@ -452,7 +454,6 @@ export interface TelegramMenuCallbackRuntimeAdapterDeps<
452
454
  text?: string,
453
455
  ) => Promise<void>;
454
456
  isIdle: (ctx: TContext) => boolean;
455
- hasActiveTelegramTurn: () => boolean;
456
457
  hasAbortHandler: () => boolean;
457
458
  getActiveToolExecutions: () => number;
458
459
  persistScopedModelPatterns?: (
@@ -464,10 +465,12 @@ export interface TelegramMenuCallbackRuntimeAdapterDeps<
464
465
  stagePendingModelSwitch: (
465
466
  selection: ScopedTelegramModel<TModel>,
466
467
  ctx: TContext,
468
+ continuationTurn: TelegramModelSwitchContinuationSource,
467
469
  ) => void;
468
470
  restartInterruptedTelegramTurn: (
469
471
  selection: ScopedTelegramModel<TModel>,
470
472
  ctx: TContext,
473
+ continuationTurn: TelegramModelSwitchContinuationSource,
471
474
  ) => Promise<boolean> | boolean;
472
475
  sectionRegistry?: TelegramSectionRegistry;
473
476
  editInteractiveMessage?: (
@@ -527,7 +530,6 @@ export function createTelegramMenuCallbackHandlerForContext<
527
530
  updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
528
531
  answerCallbackQuery: deps.answerCallbackQuery,
529
532
  isIdle: deps.isIdle,
530
- hasActiveTelegramTurn: deps.hasActiveTelegramTurn,
531
533
  hasAbortHandler: deps.hasAbortHandler,
532
534
  hasActiveToolExecutions: () => deps.getActiveToolExecutions() > 0,
533
535
  persistScopedModelPatterns: deps.persistScopedModelPatterns,
@@ -715,8 +717,7 @@ export async function handleTelegramMenuCallbackRuntime<
715
717
  activeModel: deps.getActiveModel(ctx),
716
718
  currentThinkingLevel: deps.getThinkingLevel(),
717
719
  isIdle: deps.isIdle(ctx),
718
- canRestartBusyRun:
719
- deps.hasActiveTelegramTurn() && deps.hasAbortHandler(),
720
+ canRestartBusyRun: deps.hasAbortHandler(),
720
721
  hasActiveToolExecutions: deps.hasActiveToolExecutions(),
721
722
  },
722
723
  {
@@ -733,11 +734,15 @@ export async function handleTelegramMenuCallbackRuntime<
733
734
  deps.setThinkingLevel(level);
734
735
  deps.updateStatus(ctx);
735
736
  },
736
- stagePendingModelSwitch: (selection) => {
737
- deps.stagePendingModelSwitch(selection, ctx);
737
+ stagePendingModelSwitch: (selection, continuationTurn) => {
738
+ deps.stagePendingModelSwitch(selection, ctx, continuationTurn);
738
739
  },
739
- restartInterruptedTelegramTurn: (selection) =>
740
- deps.restartInterruptedTelegramTurn(selection, ctx),
740
+ restartInterruptedTelegramTurn: (selection, continuationTurn) =>
741
+ deps.restartInterruptedTelegramTurn(
742
+ selection,
743
+ ctx,
744
+ continuationTurn,
745
+ ),
741
746
  },
742
747
  );
743
748
  } catch (error) {
@@ -374,7 +374,6 @@ export interface PendingModelSwitchStore<TSelection> {
374
374
 
375
375
  export interface TelegramInFlightModelSwitchState {
376
376
  isIdle: boolean;
377
- hasActiveTelegramTurn: boolean;
378
377
  hasAbortHandler: boolean;
379
378
  }
380
379
 
@@ -394,21 +393,21 @@ export function createPendingModelSwitchStore<
394
393
  };
395
394
  }
396
395
 
397
- export function canRestartTelegramTurnForModelSwitch(
396
+ export function canRestartAgentRunForTelegramModelSwitch(
398
397
  state: TelegramInFlightModelSwitchState,
399
398
  ): boolean {
400
- return !state.isIdle && state.hasActiveTelegramTurn && state.hasAbortHandler;
399
+ return !state.isIdle && state.hasAbortHandler;
401
400
  }
402
401
 
403
402
  export function shouldTriggerPendingTelegramModelSwitchAbort(state: {
404
403
  hasPendingModelSwitch: boolean;
405
- hasActiveTelegramTurn: boolean;
404
+ hasContinuationTurn: boolean;
406
405
  hasAbortHandler: boolean;
407
406
  activeToolExecutions: number;
408
407
  }): boolean {
409
408
  return (
410
409
  state.hasPendingModelSwitch &&
411
- state.hasActiveTelegramTurn &&
410
+ state.hasContinuationTurn &&
412
411
  state.hasAbortHandler &&
413
412
  state.activeToolExecutions === 0
414
413
  );
@@ -458,13 +457,18 @@ export function buildTelegramModelSwitchContinuationText<
458
457
  const thinkingSuffix = thinkingLevel
459
458
  ? ` Keep the selected thinking level (${thinkingLevel}) if it still applies.`
460
459
  : "";
461
- return `${telegramPrefix} Continue the interrupted previous Telegram request using the newly selected model (${modelLabel}). Resume from the last unfinished step instead of restarting from scratch unless necessary.${thinkingSuffix}`;
460
+ return `${telegramPrefix} Continue the interrupted previous request using the newly selected model (${modelLabel}). Resume from the last unfinished step instead of restarting from scratch unless necessary.${thinkingSuffix}`;
462
461
  }
463
462
 
463
+ export type TelegramModelSwitchContinuationSource = Pick<
464
+ PendingTelegramTurn,
465
+ "chatId" | "replyToMessageId" | "target"
466
+ >;
467
+
464
468
  export function buildTelegramModelSwitchContinuationTurn<
465
469
  TModel extends MenuModel,
466
470
  >(options: {
467
- turn: Pick<PendingTelegramTurn, "chatId" | "replyToMessageId" | "target">;
471
+ turn: TelegramModelSwitchContinuationSource;
468
472
  selection: ScopedTelegramModel<TModel>;
469
473
  telegramPrefix?: string;
470
474
  queueOrder: number;
@@ -494,7 +498,7 @@ export function buildTelegramModelSwitchContinuationTurn<
494
498
  ),
495
499
  },
496
500
  ],
497
- historyText: `Continue interrupted Telegram request on ${modelLabel}`,
501
+ historyText: `Continue interrupted request on ${modelLabel}`,
498
502
  statusSummary: `↻ ${statusLabel || "continue"}`,
499
503
  };
500
504
  }
@@ -506,7 +510,7 @@ export function createTelegramModelSwitchContinuationTurnBuilder<
506
510
  allocateItemOrder: () => number;
507
511
  allocateControlOrder: () => number;
508
512
  }): (options: {
509
- turn: Pick<PendingTelegramTurn, "chatId" | "replyToMessageId" | "target">;
513
+ turn: TelegramModelSwitchContinuationSource;
510
514
  selection: ScopedTelegramModel<TModel>;
511
515
  }) => PendingTelegramTurn {
512
516
  return (options) =>
@@ -523,11 +527,15 @@ export function createTelegramModelSwitchContinuationQueue<
523
527
  TSelection extends ScopedTelegramModel,
524
528
  >(deps: {
525
529
  createContinuationTurn: (options: {
526
- turn: Pick<PendingTelegramTurn, "chatId" | "replyToMessageId" | "target">;
530
+ turn: TelegramModelSwitchContinuationSource;
527
531
  selection: TSelection;
528
532
  }) => PendingTelegramTurn;
529
533
  appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
530
- }): (turn: PendingTelegramTurn, selection: TSelection, ctx: TContext) => void {
534
+ }): (
535
+ turn: TelegramModelSwitchContinuationSource,
536
+ selection: TSelection,
537
+ ctx: TContext,
538
+ ) => void {
531
539
  return (turn, selection, ctx) => {
532
540
  deps.appendQueuedItem(
533
541
  deps.createContinuationTurn({ turn, selection }),
@@ -544,7 +552,11 @@ export function createTelegramModelSwitchContinuationQueueRuntime<
544
552
  allocateItemOrder: () => number;
545
553
  allocateControlOrder: () => number;
546
554
  appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
547
- }): (turn: PendingTelegramTurn, selection: TSelection, ctx: TContext) => void {
555
+ }): (
556
+ turn: TelegramModelSwitchContinuationSource,
557
+ selection: TSelection,
558
+ ctx: TContext,
559
+ ) => void {
548
560
  return createTelegramModelSwitchContinuationQueue<TContext, TSelection>({
549
561
  createContinuationTurn: createTelegramModelSwitchContinuationTurnBuilder({
550
562
  telegramPrefix: deps.telegramPrefix,
@@ -564,7 +576,7 @@ export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
564
576
  hasAbortHandler: () => boolean;
565
577
  getActiveToolExecutions: () => number;
566
578
  queueContinuation: (
567
- turn: PendingTelegramTurn,
579
+ turn: TelegramModelSwitchContinuationSource,
568
580
  selection: TSelection,
569
581
  ctx: TContext,
570
582
  ) => void;
@@ -573,15 +585,23 @@ export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
573
585
 
574
586
  export interface TelegramModelSwitchController<TContext, TSelection> {
575
587
  canOfferInFlightSwitch: (ctx: TContext) => boolean;
576
- stagePendingSwitch: (selection: TSelection, ctx: TContext) => void;
588
+ stagePendingSwitch: (
589
+ selection: TSelection,
590
+ ctx: TContext,
591
+ continuationTurn?: TelegramModelSwitchContinuationSource,
592
+ ) => void;
577
593
  clearPendingSwitch: () => void;
578
594
  queueContinuation: (
579
- turn: PendingTelegramTurn,
595
+ turn: TelegramModelSwitchContinuationSource,
580
596
  selection: TSelection,
581
597
  ctx: TContext,
582
598
  ) => void;
583
599
  triggerPendingAbort: (ctx: TContext) => boolean;
584
- restartInterruptedTurn: (selection: TSelection, ctx: TContext) => boolean;
600
+ restartInterruptedTurn: (
601
+ selection: TSelection,
602
+ ctx: TContext,
603
+ continuationTurn?: TelegramModelSwitchContinuationSource,
604
+ ) => boolean;
585
605
  }
586
606
 
587
607
  export interface TelegramModelSwitchControllerRuntimeDeps<
@@ -624,49 +644,62 @@ export function createTelegramModelSwitchControllerRuntime<
624
644
  export function createTelegramModelSwitchController<TContext, TSelection>(
625
645
  deps: TelegramModelSwitchControllerDeps<TContext, TSelection>,
626
646
  ): TelegramModelSwitchController<TContext, TSelection> {
647
+ let pendingContinuationTurn:
648
+ | TelegramModelSwitchContinuationSource
649
+ | undefined;
650
+ const triggerPendingAbort = (ctx: TContext): boolean => {
651
+ const turn = pendingContinuationTurn ?? deps.getActiveTurn();
652
+ if (
653
+ !shouldTriggerPendingTelegramModelSwitchAbort({
654
+ hasPendingModelSwitch: !!deps.getPendingModelSwitch(),
655
+ hasContinuationTurn: !!turn,
656
+ hasAbortHandler: deps.hasAbortHandler(),
657
+ activeToolExecutions: deps.getActiveToolExecutions(),
658
+ })
659
+ ) {
660
+ return false;
661
+ }
662
+ const selection = deps.getPendingModelSwitch();
663
+ const abort = deps.getAbortHandler();
664
+ if (!selection || !turn || !abort) return false;
665
+ pendingContinuationTurn = undefined;
666
+ deps.setPendingModelSwitch(undefined);
667
+ deps.queueContinuation(turn, selection, ctx);
668
+ abort();
669
+ return true;
670
+ };
627
671
  return {
628
672
  canOfferInFlightSwitch: (ctx) =>
629
- canRestartTelegramTurnForModelSwitch({
673
+ canRestartAgentRunForTelegramModelSwitch({
630
674
  isIdle: deps.isIdle(ctx),
631
- hasActiveTelegramTurn: !!deps.getActiveTurn(),
632
675
  hasAbortHandler: deps.hasAbortHandler(),
633
676
  }),
634
- stagePendingSwitch: (selection, ctx) => {
677
+ stagePendingSwitch: (selection, ctx, continuationTurn) => {
678
+ pendingContinuationTurn = deps.getActiveTurn() ?? continuationTurn;
635
679
  deps.setPendingModelSwitch(selection);
636
- deps.updateStatus(ctx);
680
+ try {
681
+ deps.updateStatus(ctx);
682
+ } finally {
683
+ triggerPendingAbort(ctx);
684
+ }
637
685
  },
638
686
  clearPendingSwitch: () => {
687
+ pendingContinuationTurn = undefined;
639
688
  deps.setPendingModelSwitch(undefined);
640
689
  },
641
690
  queueContinuation: deps.queueContinuation,
642
- triggerPendingAbort: (ctx) => {
643
- if (
644
- !shouldTriggerPendingTelegramModelSwitchAbort({
645
- hasPendingModelSwitch: !!deps.getPendingModelSwitch(),
646
- hasActiveTelegramTurn: !!deps.getActiveTurn(),
647
- hasAbortHandler: deps.hasAbortHandler(),
648
- activeToolExecutions: deps.getActiveToolExecutions(),
649
- })
650
- ) {
651
- return false;
652
- }
653
- const selection = deps.getPendingModelSwitch();
654
- const turn = deps.getActiveTurn();
655
- const abort = deps.getAbortHandler();
656
- if (!selection || !turn || !abort) return false;
657
- deps.setPendingModelSwitch(undefined);
658
- deps.queueContinuation(turn, selection, ctx);
659
- abort();
660
- return true;
661
- },
662
- restartInterruptedTurn: (selection, ctx) =>
663
- restartTelegramModelSwitchContinuation({
664
- activeTurn: deps.getActiveTurn(),
691
+ triggerPendingAbort,
692
+ restartInterruptedTurn: (selection, ctx, continuationTurn) => {
693
+ const restarted = restartTelegramModelSwitchContinuation({
694
+ activeTurn: deps.getActiveTurn() ?? continuationTurn,
665
695
  abort: deps.getAbortHandler(),
666
696
  selection,
667
697
  queueContinuation: (turn, nextSelection) => {
668
698
  deps.queueContinuation(turn, nextSelection, ctx);
669
699
  },
670
- }),
700
+ });
701
+ if (restarted) pendingContinuationTurn = undefined;
702
+ return restarted;
703
+ },
671
704
  };
672
705
  }
@@ -1253,7 +1253,6 @@ export interface TelegramAgentStartHookRuntimeDeps<
1253
1253
  export type TelegramAgentStartHookEvent = unknown;
1254
1254
 
1255
1255
  export interface TelegramToolExecutionRuntimeDeps {
1256
- hasActiveTurn: () => boolean;
1257
1256
  getActiveToolExecutions: () => number;
1258
1257
  setActiveToolExecutions: (count: number) => void;
1259
1258
  }
@@ -1361,11 +1360,9 @@ export function createTelegramAgentStartHook<
1361
1360
  }
1362
1361
 
1363
1362
  export function getNextTelegramToolExecutionCount(options: {
1364
- hasActiveTurn: boolean;
1365
1363
  currentCount: number;
1366
1364
  event: "start" | "end";
1367
1365
  }): number {
1368
- if (!options.hasActiveTurn) return options.currentCount;
1369
1366
  if (options.event === "start") {
1370
1367
  return options.currentCount + 1;
1371
1368
  }
@@ -1377,7 +1374,6 @@ export function handleTelegramToolExecutionStartRuntime(
1377
1374
  ): void {
1378
1375
  deps.setActiveToolExecutions(
1379
1376
  getNextTelegramToolExecutionCount({
1380
- hasActiveTurn: deps.hasActiveTurn(),
1381
1377
  currentCount: deps.getActiveToolExecutions(),
1382
1378
  event: "start",
1383
1379
  }),
@@ -1387,15 +1383,13 @@ export function handleTelegramToolExecutionStartRuntime(
1387
1383
  export function handleTelegramToolExecutionEndRuntime(
1388
1384
  deps: TelegramToolExecutionEndRuntimeDeps,
1389
1385
  ): void {
1390
- const hasActiveTurn = deps.hasActiveTurn();
1391
1386
  deps.setActiveToolExecutions(
1392
1387
  getNextTelegramToolExecutionCount({
1393
- hasActiveTurn,
1394
1388
  currentCount: deps.getActiveToolExecutions(),
1395
1389
  event: "end",
1396
1390
  }),
1397
1391
  );
1398
- if (hasActiveTurn) deps.triggerPendingModelSwitchAbort();
1392
+ deps.triggerPendingModelSwitchAbort();
1399
1393
  }
1400
1394
 
1401
1395
  export type TelegramAgentLifecycleHooksRuntimeDeps<
@@ -1485,7 +1479,6 @@ export function createTelegramToolExecutionHooks<TContext>(
1485
1479
  ctx: TContext,
1486
1480
  ): void => {
1487
1481
  handleTelegramToolExecutionEndRuntime({
1488
- hasActiveTurn: deps.hasActiveTurn,
1489
1482
  getActiveToolExecutions: deps.getActiveToolExecutions,
1490
1483
  setActiveToolExecutions: deps.setActiveToolExecutions,
1491
1484
  triggerPendingModelSwitchAbort: () => {
@@ -1010,7 +1010,6 @@ export function createTelegramInboundRouteRuntime<
1010
1010
  updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
1011
1011
  answerCallbackQuery: deps.answerCallbackQuery,
1012
1012
  isIdle: deps.isIdle,
1013
- hasActiveTelegramTurn: deps.activeTurnRuntime.has,
1014
1013
  hasAbortHandler: deps.bridgeRuntime.abort.hasHandler,
1015
1014
  getActiveToolExecutions:
1016
1015
  deps.bridgeRuntime.lifecycle.getActiveToolExecutions,
@@ -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 = 2500;
7
+ const TELEGRAM_TYPING_ACTION_INTERVAL_MS = 3_000;
8
8
  const TELEGRAM_TYPING_IDLE_DRAIN_MAX_MS = 250;
9
9
 
10
10
  export interface TelegramRuntimeQueueCounters {
@@ -319,7 +319,6 @@ export interface TelegramTypingLoopDeps {
319
319
  chatId: number,
320
320
  options?: { message_thread_id?: number },
321
321
  ) => Promise<unknown>;
322
- sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
323
322
  shouldContinue?: () => boolean;
324
323
  onStopped?: () => void;
325
324
  }
@@ -364,7 +363,6 @@ export interface TelegramTypingLoopStarterDeps<
364
363
  chatId: number,
365
364
  options?: { message_thread_id?: number },
366
365
  ) => Promise<unknown>;
367
- sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
368
366
  updateStatus: (ctx: TContext, error?: string) => void;
369
367
  isContextActive?: (ctx: TContext) => boolean;
370
368
  isTransportAvailable?: () => boolean;
@@ -378,7 +376,7 @@ export function createTelegramTypingLoopStarter<TContext>(
378
376
  ctx: TContext,
379
377
  chatId?: number,
380
378
  options?: { target?: TelegramTypingLoopTarget },
381
- ) => void {
379
+ ) => boolean {
382
380
  return (ctx, chatId, options) => {
383
381
  const transportAuthority = deps.getTransportAuthority?.();
384
382
  const hasTransport = (): boolean =>
@@ -386,9 +384,9 @@ export function createTelegramTypingLoopStarter<TContext>(
386
384
  ? transportAuthority !== undefined &&
387
385
  Object.is(deps.getTransportAuthority(), transportAuthority)
388
386
  : deps.isTransportAvailable?.() !== false;
389
- if (!hasTransport()) return;
387
+ if (!hasTransport()) return false;
390
388
  let active = true;
391
- deps.typing.start({
389
+ return deps.typing.start({
392
390
  chatId: chatId ?? deps.getDefaultChatId(),
393
391
  target: options?.target,
394
392
  intervalMs: deps.intervalMs ?? TELEGRAM_TYPING_ACTION_INTERVAL_MS,
@@ -407,14 +405,6 @@ export function createTelegramTypingLoopStarter<TContext>(
407
405
  deps.typing.stop();
408
406
  return;
409
407
  }
410
- const message =
411
- error instanceof Error ? error.message : String(error);
412
- updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
413
- error: message,
414
- category: "typing",
415
- phase: "status-update",
416
- recordRuntimeEvent: deps.recordRuntimeEvent,
417
- });
418
408
  try {
419
409
  deps.recordRuntimeEvent?.("typing", error, {
420
410
  chatId: targetChatId,
@@ -424,41 +414,6 @@ export function createTelegramTypingLoopStarter<TContext>(
424
414
  }
425
415
  }
426
416
  },
427
- sendAggregateTypingAction: deps.sendAggregateTypingAction
428
- ? async (targetChatId) => {
429
- if (!active) return;
430
- if (!hasTransport()) {
431
- deps.typing.stop();
432
- return;
433
- }
434
- try {
435
- await deps.sendAggregateTypingAction?.(targetChatId);
436
- } catch (error) {
437
- if (deps.isContextActive?.(ctx) === false) return;
438
- if (!active) return;
439
- if (!hasTransport()) {
440
- deps.typing.stop();
441
- return;
442
- }
443
- const message =
444
- error instanceof Error ? error.message : String(error);
445
- updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
446
- error: message,
447
- category: "typing",
448
- phase: "status-update",
449
- recordRuntimeEvent: deps.recordRuntimeEvent,
450
- });
451
- try {
452
- deps.recordRuntimeEvent?.("typing", error, {
453
- chatId: targetChatId,
454
- aggregate: true,
455
- });
456
- } catch {
457
- // Typing diagnostics cannot escape the in-flight action owner.
458
- }
459
- }
460
- }
461
- : undefined,
462
417
  shouldContinue: hasTransport,
463
418
  onStopped: () => {
464
419
  active = false;
@@ -502,12 +457,7 @@ export function startTelegramTypingLoop(
502
457
  const targetChatId = activeDeps.chatId;
503
458
  const threadParams = getTelegramTypingLoopThreadParams(activeDeps.target);
504
459
  const typing = Promise.resolve()
505
- .then(async () => {
506
- await activeDeps.sendTypingAction(targetChatId, threadParams);
507
- if (threadParams?.message_thread_id !== undefined) {
508
- await activeDeps.sendAggregateTypingAction?.(targetChatId);
509
- }
510
- })
460
+ .then(() => activeDeps.sendTypingAction(targetChatId, threadParams))
511
461
  .then(() => undefined)
512
462
  .catch(() => undefined);
513
463
  state.typingInFlight = typing;
@@ -602,7 +552,7 @@ export interface TelegramPromptDispatchLifecycleDeps<
602
552
  ctx: TContext,
603
553
  chatId?: number,
604
554
  options?: { target?: TelegramTypingLoopTarget },
605
- ) => void;
555
+ ) => boolean | void;
606
556
  updateStatus: (ctx: TContext, error?: string) => void;
607
557
  }
608
558
 
@@ -616,7 +566,6 @@ export interface TelegramPromptDispatchRuntimeDeps<
616
566
  chatId: number,
617
567
  options?: { message_thread_id?: number },
618
568
  ) => Promise<unknown>;
619
- sendAggregateTypingAction?: (chatId: number) => Promise<unknown>;
620
569
  updateStatus: (ctx: TContext, error?: string) => void;
621
570
  isContextActive?: (ctx: TContext) => boolean;
622
571
  isTransportAvailable?: () => boolean;
@@ -629,7 +578,7 @@ export interface TelegramPromptDispatchRuntime<TContext> {
629
578
  ctx: TContext,
630
579
  chatId?: number,
631
580
  options?: { target?: TelegramTypingLoopTarget },
632
- ) => void;
581
+ ) => boolean | void;
633
582
  onPromptDispatchStart: (ctx: TContext, chatId?: number) => void;
634
583
  onPromptDispatchFailure: (ctx: TContext, message: string) => void;
635
584
  }
@@ -1,21 +1,29 @@
1
1
  /**
2
2
  * Bundled Telegram skill discovery
3
3
  * Zones: pi agent, telegram guidance
4
- * Owns source-checkout and installed-package skill path contribution
4
+ * Owns source-checkout skill contribution; installed packages use their manifest
5
5
  */
6
6
 
7
+ import { extname } from "node:path";
7
8
  import { fileURLToPath } from "node:url";
8
9
 
9
10
  import type { ExtensionAPI } from "./pi.ts";
10
11
 
12
+ const TELEGRAM_SKILLS_MODULE_PATH = fileURLToPath(import.meta.url);
13
+
11
14
  export const TELEGRAM_SKILLS_PATH = fileURLToPath(
12
15
  new URL("../skills", import.meta.url),
13
16
  );
14
17
 
15
18
  export function registerTelegramSkillDiscovery(
16
19
  pi: Pick<ExtensionAPI, "on">,
17
- ): void {
20
+ modulePath = TELEGRAM_SKILLS_MODULE_PATH,
21
+ ): boolean {
22
+ // A raw source extension has no package manifest owner. Compiled npm/git
23
+ // packages do, so contributing again would bypass their resource filters.
24
+ if (extname(modulePath) !== ".ts") return false;
18
25
  pi.on("resources_discover", () => ({
19
26
  skillPaths: [TELEGRAM_SKILLS_PATH],
20
27
  }));
28
+ return true;
21
29
  }