@llblab/pi-kit 0.33.0 → 0.34.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.
Files changed (67) hide show
  1. package/BACKLOG.md +2 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +3 -3
  4. package/node_modules/@llblab/pi-telegram/AGENTS.md +5 -4
  5. package/node_modules/@llblab/pi-telegram/BACKLOG.md +6 -1
  6. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +20 -0
  7. package/node_modules/@llblab/pi-telegram/README.md +29 -18
  8. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +8 -4
  9. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +41 -1
  10. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +2 -0
  11. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +10 -5
  12. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +2 -0
  13. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +34 -20
  14. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +10 -0
  15. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +32 -13
  16. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +1 -1
  17. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +33 -28
  18. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +18 -3
  19. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -1
  20. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +28 -20
  21. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +6 -2
  22. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +3 -3
  23. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +1 -0
  24. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +102 -99
  25. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +3 -2
  26. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +3 -0
  27. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +6 -2
  28. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -3
  29. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +57 -5
  30. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +313 -25
  31. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +5 -1
  32. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +323 -30
  33. package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +1 -2
  34. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +6 -7
  35. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +11 -1
  36. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +11 -0
  37. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +57 -6
  38. package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +4 -0
  39. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +6 -0
  40. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +14 -7
  41. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +44 -9
  42. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  43. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
  44. package/node_modules/@llblab/pi-telegram/docs/architecture.md +27 -14
  45. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +4 -0
  46. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +8 -4
  47. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +14 -10
  48. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +52 -3
  49. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +13 -5
  50. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +37 -23
  51. package/node_modules/@llblab/pi-telegram/lib/bus.ts +36 -10
  52. package/node_modules/@llblab/pi-telegram/lib/commands.ts +52 -47
  53. package/node_modules/@llblab/pi-telegram/lib/extension.ts +18 -2
  54. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +34 -28
  55. package/node_modules/@llblab/pi-telegram/lib/locks.ts +5 -2
  56. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +116 -162
  57. package/node_modules/@llblab/pi-telegram/lib/pi.ts +8 -0
  58. package/node_modules/@llblab/pi-telegram/lib/polling.ts +7 -2
  59. package/node_modules/@llblab/pi-telegram/lib/queue.ts +328 -33
  60. package/node_modules/@llblab/pi-telegram/lib/routing.ts +350 -30
  61. package/node_modules/@llblab/pi-telegram/lib/status.ts +7 -7
  62. package/node_modules/@llblab/pi-telegram/lib/sync.ts +11 -0
  63. package/node_modules/@llblab/pi-telegram/lib/threads.ts +57 -5
  64. package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
  65. package/node_modules/@llblab/pi-telegram/lib/updates.ts +46 -15
  66. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  67. package/package.json +2 -2
@@ -26,6 +26,8 @@ import type {
26
26
  ToolExecutionEndEvent,
27
27
  ToolExecutionStartEvent,
28
28
  ToolExecutionUpdateEvent,
29
+ TurnEndEvent,
30
+ TurnEndEventResult,
29
31
  UIPromptEndEvent,
30
32
  UIPromptStartEvent,
31
33
  } from "./pi.ts";
@@ -100,6 +102,7 @@ export interface TelegramLifecycleRegistrationDeps {
100
102
  event: { model: TelegramLifecycleModel },
101
103
  ctx: ExtensionContext,
102
104
  ) => Promise<void> | void;
105
+ onTurnEnd?: (event: TurnEndEvent, ctx: ExtensionContext) => TurnEndEventResult | undefined;
103
106
  onAgentStart: (
104
107
  event: AgentStartEvent,
105
108
  ctx: ExtensionContext,
@@ -575,7 +578,6 @@ export function createTelegramBridgeSessionLifecycleAssembly<
575
578
  >(
576
579
  deps: TelegramBridgeSessionLifecycleAssemblyDeps<TQueueItem, TModel>,
577
580
  ): TelegramSessionLifecycleHooks {
578
- const isSessionActive = deps.contextStore.isCurrent;
579
581
  const suspendForReplacement =
580
582
  BusFollower.createTelegramBusFollowerSessionReplacementSuspender({
581
583
  registrationState: deps.follower.registrationState,
@@ -583,32 +585,44 @@ export function createTelegramBridgeSessionLifecycleAssembly<
583
585
  suspendPolling: deps.follower.suspendPolling,
584
586
  recordRuntimeEvent: deps.follower.recordRuntimeEvent,
585
587
  });
586
- let preserveTarget = true;
587
- const queueLifecycle = Queue.createTelegramSessionLifecycleRuntime({
588
- ...deps.queue,
589
- isSessionActive,
590
- stopPolling: () => suspendForReplacement(preserveTarget),
591
- clearPendingMediaGroups: deps.services.suspendGroupedInput,
592
- });
588
+ // A reused context can be current for a successor; inner awaits need this invocation's generation too.
589
+ const queueLifecycle = (ctx: ExtensionContext, generation: number, preserveTarget = true) =>
590
+ Queue.createTelegramSessionLifecycleRuntime({
591
+ ...deps.queue,
592
+ isSessionActive: () => deps.contextStore.isCurrent(ctx, generation),
593
+ stopPolling: () => suspendForReplacement(preserveTarget),
594
+ clearPendingMediaGroups: deps.services.suspendGroupedInput,
595
+ });
593
596
  const servicesLifecycle: TelegramSessionLifecycleHooks = {
594
597
  async onSessionStart(event, ctx) {
598
+ const generation = deps.contextStore.getGeneration();
599
+ const isCurrent = () => deps.contextStore.isCurrent(ctx, generation);
595
600
  const resumeConnection = deps.services.connection?.prepare(event, ctx);
596
- await queueLifecycle.onSessionStart(event, ctx);
597
- if (!isSessionActive(ctx)) return;
601
+ await queueLifecycle(ctx, generation).onSessionStart(event, ctx);
602
+ if (!isCurrent()) return;
598
603
  deps.services.resumeGroupedInput(ctx);
599
604
  await deps.services.delivery.onSessionStart();
600
- if (!isSessionActive(ctx)) return;
605
+ if (!isCurrent()) return;
601
606
  if (resumeConnection) resumeConnection();
602
607
  else await deps.services.polling.onSessionStart(event, ctx);
608
+ if (!isCurrent()) return;
603
609
  deps.services.capabilityMonitor.start(ctx);
604
610
  deps.services.queueWatchdog.start(ctx);
611
+ await BusFollower.createTelegramBusFollowerSessionRefreshHook({
612
+ registrationState: deps.follower.registrationState,
613
+ registrationRuntime: deps.follower.registrationRuntime,
614
+ getLeaderState: deps.follower.getLeaderState,
615
+ isSessionActive: isCurrent,
616
+ updateStatus: deps.follower.updateStatus,
617
+ recordRuntimeEvent: deps.follower.recordRuntimeEvent,
618
+ })(event, ctx);
605
619
  },
606
620
  async onSessionShutdown(event, ctx) {
607
621
  const generation = deps.contextStore.getGeneration();
608
622
  const isCurrent = () => deps.contextStore.isCurrent(ctx, generation);
609
623
  if (!isCurrent()) return;
610
624
  deps.services.connection?.onSessionShutdown(event, ctx);
611
- preserveTarget = event.reason !== "resume";
625
+ const preserveTarget = event.reason !== "resume";
612
626
  let preserveThread: (() => Promise<void>) | undefined;
613
627
  if (event.reason === "quit") {
614
628
  try {
@@ -625,7 +639,7 @@ export function createTelegramBridgeSessionLifecycleAssembly<
625
639
  if (!isCurrent()) return;
626
640
  deps.services.queueWatchdog.stop();
627
641
  deps.services.capabilityMonitor.stop();
628
- await queueLifecycle.onSessionShutdown(event, ctx);
642
+ await queueLifecycle(ctx, generation, preserveTarget).onSessionShutdown(event, ctx);
629
643
  if (!isCurrent()) return;
630
644
  await deps.services.inboundWorker.onSessionShutdown();
631
645
  if (!isCurrent()) return;
@@ -638,23 +652,9 @@ export function createTelegramBridgeSessionLifecycleAssembly<
638
652
  }
639
653
  },
640
654
  };
641
- const followerLifecycle = appendTelegramLifecycleHooks(
642
- servicesLifecycle,
643
- {
644
- onSessionStart: BusFollower.createTelegramBusFollowerSessionRefreshHook({
645
- registrationState: deps.follower.registrationState,
646
- registrationRuntime: deps.follower.registrationRuntime,
647
- getLeaderState: deps.follower.getLeaderState,
648
- isSessionActive,
649
- updateStatus: deps.follower.updateStatus,
650
- recordRuntimeEvent: deps.follower.recordRuntimeEvent,
651
- }),
652
- },
653
- isSessionActive,
654
- );
655
655
  return createTelegramSessionGenerationFence(
656
656
  deps.contextStore,
657
- followerLifecycle,
657
+ servicesLifecycle,
658
658
  );
659
659
  }
660
660
 
@@ -1042,6 +1042,12 @@ export function registerTelegramLifecycleHooks(
1042
1042
  if (!isActive(ctx)) return;
1043
1043
  await deps.onAgentStart(event, ctx);
1044
1044
  });
1045
+ if (deps.onTurnEnd) {
1046
+ pi.on("turn_end", (event, ctx) => {
1047
+ if (!isActive(ctx)) return;
1048
+ return deps.onTurnEnd?.(event, ctx);
1049
+ });
1050
+ }
1045
1051
  pi.on("tool_execution_start", async (event, ctx) => {
1046
1052
  if (!isActive(ctx)) return;
1047
1053
  await deps.onToolExecutionStart(event, ctx);
@@ -1981,6 +1981,7 @@ export interface TelegramLockedPollingRuntime<
1981
1981
  registerFollowerWithOwner?: (
1982
1982
  ctx: TContext,
1983
1983
  owner: TelegramLockEntry,
1984
+ options?: { recoverConnection?: boolean },
1984
1985
  ) => boolean | undefined | Promise<boolean | undefined>;
1985
1986
  restoreFollowerWithOwner?: (
1986
1987
  ctx: TContext,
@@ -2008,6 +2009,7 @@ interface TelegramLockedPollingRuntimeDeps<
2008
2009
  registerFollowerWithOwner?: (
2009
2010
  ctx: TContext,
2010
2011
  owner: TelegramLockEntry,
2012
+ options?: { recoverConnection?: boolean },
2011
2013
  ) => boolean | undefined | Promise<boolean | undefined>;
2012
2014
  restoreFollowerWithOwner?: (
2013
2015
  ctx: TContext,
@@ -2294,6 +2296,7 @@ export function createTelegramLockedPollingRuntime<
2294
2296
  const registered = await deps.registerFollowerWithOwner(
2295
2297
  ctx,
2296
2298
  acquired.lock,
2299
+ options.forceFreshLeaderThread ? { recoverConnection: true } : undefined,
2297
2300
  );
2298
2301
  if (!isCurrent()) return cancelled;
2299
2302
  if (registered) {
@@ -2518,8 +2521,8 @@ export function createTelegramLockedPollingRuntime<
2518
2521
  sessionAutoStartRun = run;
2519
2522
  },
2520
2523
  registerFollowerWithOwner: deps.registerFollowerWithOwner
2521
- ? async (ctx, owner) => {
2522
- const registered = await deps.registerFollowerWithOwner?.(ctx, owner);
2524
+ ? async (ctx, owner, options) => {
2525
+ const registered = await deps.registerFollowerWithOwner?.(ctx, owner, options);
2523
2526
  if (registered) deps.updateStatus(ctx);
2524
2527
  return registered === true;
2525
2528
  }