@llblab/pi-kit 0.6.0 → 0.7.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 (123) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +4 -4
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
  4. package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
  5. package/node_modules/@llblab/pi-actors/README.md +1 -1
  6. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
  7. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
  9. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
  10. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
  11. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
  12. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
  13. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
  14. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
  17. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
  18. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
  19. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
  20. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
  21. package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
  22. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
  23. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
  24. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
  25. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
  26. package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
  27. package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
  28. package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
  29. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
  30. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
  31. package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
  32. package/node_modules/@llblab/pi-actors/package.json +1 -1
  33. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
  34. package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
  35. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
  36. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
  37. package/node_modules/@llblab/pi-state-flow/README.md +203 -107
  38. package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
  39. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
  40. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
  41. package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
  42. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
  43. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
  44. package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
  45. package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
  46. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
  47. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
  48. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
  49. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
  50. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
  51. package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
  52. package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
  53. package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
  54. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
  55. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
  57. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
  58. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
  59. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
  60. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
  61. package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
  62. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
  63. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
  64. package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
  65. package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
  66. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
  68. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
  69. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
  70. package/node_modules/@llblab/pi-state-flow/package.json +8 -2
  71. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
  72. package/node_modules/@llblab/pi-telegram/AGENTS.md +13 -8
  73. package/node_modules/@llblab/pi-telegram/BACKLOG.md +20 -3
  74. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  75. package/node_modules/@llblab/pi-telegram/README.md +12 -8
  76. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
  77. package/node_modules/@llblab/pi-telegram/docs/architecture.md +220 -18
  78. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  79. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
  80. package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
  81. package/node_modules/@llblab/pi-telegram/docs/public-api.md +12 -5
  82. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
  83. package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
  84. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
  85. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +28 -1
  86. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
  87. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
  88. package/node_modules/@llblab/pi-telegram/lib/bus.ts +350 -26
  89. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +544 -0
  90. package/node_modules/@llblab/pi-telegram/lib/commands.ts +234 -11
  91. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -24
  92. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1792 -0
  93. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
  94. package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
  95. package/node_modules/@llblab/pi-telegram/lib/locks.ts +38 -1
  96. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
  97. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +51 -10
  98. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
  99. package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
  100. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +5 -2
  101. package/node_modules/@llblab/pi-telegram/lib/queue.ts +52 -18
  102. package/node_modules/@llblab/pi-telegram/lib/replies.ts +2 -2
  103. package/node_modules/@llblab/pi-telegram/lib/routing.ts +305 -112
  104. package/node_modules/@llblab/pi-telegram/lib/status.ts +10 -0
  105. package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
  106. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +279 -4
  107. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
  108. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
  109. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
  110. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
  111. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
  112. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
  113. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
  114. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
  115. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  116. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  117. package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
  118. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
  119. package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
  120. package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
  121. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
  122. package/node_modules/@llblab/skills/package.json +2 -3
  123. package/package.json +6 -5
@@ -280,11 +280,12 @@ export type TelegramDurablePollingRuntimeAssemblyDeps<
280
280
  "appendUpdateBatch" | "getJournalEntryCount" | "signalUpdateWorker"
281
281
  > & {
282
282
  canStart?: (ctx: TContext) => boolean;
283
+ prepareUpdateBatch?: (updates: readonly TUpdate[]) => void;
283
284
  journal: {
284
285
  appendBatch: (
285
286
  updates: readonly TUpdate[],
286
287
  acceptedThroughUpdateId?: number,
287
- ) => MaybePromise<unknown>;
288
+ ) => { nonExcludedUpdateIds: readonly number[] };
288
289
  getAcceptedThroughUpdateId: () => number | undefined;
289
290
  prepareCursorCutover?: () => MaybePromise<void>;
290
291
  getEntryCount: () => number;
@@ -303,7 +304,30 @@ export function createTelegramDurablePollingRuntimeAssembly<
303
304
  ): TelegramDurablePollingRuntimeAssembly<TContext> {
304
305
  const controller = createTelegramPollingControllerRuntime({
305
306
  ...deps,
306
- appendUpdateBatch: deps.journal.appendBatch,
307
+ appendUpdateBatch(updates, cursor) {
308
+ const result = deps.journal.appendBatch(updates, cursor);
309
+ if (!deps.prepareUpdateBatch) return result;
310
+ // No await: an already-draining worker must not observe the new batch before preparation.
311
+ try {
312
+ const included = new Set(result.nonExcludedUpdateIds);
313
+ let batch: TUpdate[] = [];
314
+ for (const update of updates) {
315
+ if (included.has(update.update_id)) batch.push(update);
316
+ else if (batch.length > 0) {
317
+ deps.prepareUpdateBatch(batch);
318
+ batch = [];
319
+ }
320
+ }
321
+ if (batch.length > 0) deps.prepareUpdateBatch(batch);
322
+ } catch (error) {
323
+ try {
324
+ deps.recordRuntimeEvent?.("polling", error, { phase: "batch-preparation", updateCount: updates.length });
325
+ } catch {
326
+ // Already-published input must still reach the worker if diagnostics fail.
327
+ }
328
+ }
329
+ return result;
330
+ },
307
331
  getAcceptedThroughUpdateId: deps.journal.getAcceptedThroughUpdateId,
308
332
  getJournalEntryCount: deps.journal.getEntryCount,
309
333
  signalUpdateWorker: deps.journal.signalWorker,
@@ -393,7 +417,6 @@ export function createTelegramPollingControllerRuntime<
393
417
  getAcceptedThroughUpdateId: deps.getAcceptedThroughUpdateId,
394
418
  getJournalEntryCount: deps.getJournalEntryCount,
395
419
  signalUpdateWorker: deps.signalUpdateWorker,
396
- prepareUpdateBatch: deps.prepareUpdateBatch,
397
420
  updateStatus: deps.updateStatus,
398
421
  sleep: deps.sleep,
399
422
  onPhaseChange(phase, currentUpdateId) {
@@ -651,6 +674,7 @@ export interface TelegramThreadCapabilityRuntimeDeps<
651
674
  getPollingStartedWithTelegramBus: () => boolean;
652
675
  setPollingStartedWithTelegramBus: (started: boolean) => void;
653
676
  setTopicModeUnavailable: (unavailable: boolean) => void;
677
+ suspendLiveThreadTarget?: () => void;
654
678
  stopFollowerRegistration: () => void;
655
679
  startClassicPolling: (ctx: TContext) => MaybePromise<void>;
656
680
  stopClassicPolling: () => MaybePromise<void>;
@@ -682,6 +706,8 @@ export interface TelegramThreadCapabilityStateRuntime {
682
706
  isBusRuntimeEnabled(): boolean;
683
707
  shouldForceFreshLeaderThread(): boolean;
684
708
  setForceFreshLeaderThread(forceFresh: boolean): void;
709
+ getRequestedThreadName(): string | undefined;
710
+ setRequestedThreadName(threadName: string | undefined): void;
685
711
  }
686
712
 
687
713
  export type TelegramThreadTargetObservationHandler<TContext> = (
@@ -715,6 +741,10 @@ export interface TelegramThreadAwarePollingPorts<TContext, TOwner> {
715
741
  ctx: TContext,
716
742
  owner: TOwner,
717
743
  ) => Promise<boolean | undefined>;
744
+ restoreFollowerWithOwner: (
745
+ ctx: TContext,
746
+ owner: TOwner,
747
+ ) => Promise<boolean | undefined>;
718
748
  stopFollowerRegistration: () => void;
719
749
  }
720
750
 
@@ -738,6 +768,11 @@ export interface TelegramThreadAwarePollingDeps<
738
768
  ctx: TContext,
739
769
  owner: TOwner,
740
770
  ) => Promise<boolean | undefined>;
771
+ restoreFollowerWithLeader?: (
772
+ ctx: TContext,
773
+ owner: TOwner,
774
+ ) => Promise<boolean | undefined>;
775
+ hasRememberedWorkspaceBinding?: (ctx: TContext) => boolean;
741
776
  stopFollowerRegistration: () => void;
742
777
  }
743
778
 
@@ -760,6 +795,12 @@ export interface TelegramThreadCapabilityOrchestrationDeps<
760
795
  ctx: TContext,
761
796
  owner: TOwner,
762
797
  ) => Promise<boolean | undefined>;
798
+ restoreFollowerWithLeader?: (
799
+ ctx: TContext,
800
+ owner: TOwner,
801
+ ) => Promise<boolean | undefined>;
802
+ hasRememberedWorkspaceBinding?: (ctx: TContext) => boolean;
803
+ suspendLiveThreadTarget?: () => void;
763
804
  stopFollowerRegistration: () => void;
764
805
  isTopicModeUnavailableError: (error: unknown) => boolean;
765
806
  updateStatus: (ctx: TContext) => void;
@@ -780,6 +821,7 @@ export function createTelegramThreadCapabilityStateRuntime(): TelegramThreadCapa
780
821
  let busPollingStarted = false;
781
822
  let topicModeUnavailable = false;
782
823
  let forceFreshLeaderThread = false;
824
+ let requestedThreadName: string | undefined;
783
825
  return {
784
826
  isBusPollingStarted: () => busPollingStarted,
785
827
  setBusPollingStarted(started) {
@@ -794,6 +836,10 @@ export function createTelegramThreadCapabilityStateRuntime(): TelegramThreadCapa
794
836
  setForceFreshLeaderThread(forceFresh) {
795
837
  forceFreshLeaderThread = forceFresh;
796
838
  },
839
+ getRequestedThreadName: () => requestedThreadName,
840
+ setRequestedThreadName(threadName) {
841
+ requestedThreadName = threadName;
842
+ },
797
843
  };
798
844
  }
799
845
 
@@ -818,6 +864,7 @@ export function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(
818
864
  getPollingStartedWithTelegramBus: deps.state.isBusPollingStarted,
819
865
  setPollingStartedWithTelegramBus: deps.state.setBusPollingStarted,
820
866
  setTopicModeUnavailable: deps.state.setTopicModeUnavailable,
867
+ suspendLiveThreadTarget: deps.suspendLiveThreadTarget,
821
868
  stopFollowerRegistration: deps.stopFollowerRegistration,
822
869
  startClassicPolling: deps.startClassicPolling,
823
870
  stopClassicPolling: deps.stopClassicPolling,
@@ -850,6 +897,8 @@ export function createTelegramThreadCapabilityOrchestration<TContext, TOwner>(
850
897
  startLeaderHealth: deps.startLeaderHealth,
851
898
  stopLeaderHealth: deps.stopLeaderHealth,
852
899
  registerFollowerWithLeader: deps.registerFollowerWithLeader,
900
+ restoreFollowerWithLeader: deps.restoreFollowerWithLeader,
901
+ hasRememberedWorkspaceBinding: deps.hasRememberedWorkspaceBinding,
853
902
  stopFollowerRegistration: deps.stopFollowerRegistration,
854
903
  recordEvent: deps.recordEvent,
855
904
  setTopicModeUnavailable: deps.state.setTopicModeUnavailable,
@@ -958,14 +1007,16 @@ export async function applyTelegramThreadCapability<TContext>(
958
1007
  if (!isCurrent()) return;
959
1008
  deps.setTopicModeUnavailable(true);
960
1009
  deps.stopFollowerRegistration();
1010
+ const hadLiveThreadTransport = deps.getPollingStartedWithTelegramBus();
961
1011
  if (
962
- deps.getPollingStartedWithTelegramBus() ||
1012
+ hadLiveThreadTransport ||
963
1013
  hasTelegramClassicRestoreFailure(previousBotState)
964
1014
  ) {
965
1015
  deps.stopLeaderHealth();
966
1016
  await deps.stopBusPolling();
967
1017
  if (!isCurrent()) return;
968
1018
  deps.setPollingStartedWithTelegramBus(false);
1019
+ if (hadLiveThreadTransport) deps.suspendLiveThreadTarget?.();
969
1020
  try {
970
1021
  await deps.startClassicPolling(ctx);
971
1022
  if (!isCurrent()) return;
@@ -1110,24 +1161,34 @@ export function createTelegramThreadAwarePollingPorts<TContext, TOwner>(
1110
1161
  }
1111
1162
  await deps.stopClassicPolling();
1112
1163
  };
1113
- const registerFollowerWithOwner = async (
1114
- ctx: TContext,
1115
- owner: TOwner,
1116
- ): Promise<boolean | undefined> => {
1164
+ const refreshFollowerState = async (): Promise<boolean> => {
1117
1165
  if (deps.topicTargetStore.refresh) {
1118
1166
  await deps.topicTargetStore.refresh();
1119
1167
  } else {
1120
1168
  await deps.topicTargetStore.load();
1121
1169
  }
1122
- if (deps.topicTargetStore.getBotState().threadMode !== "enabled") {
1123
- return undefined;
1124
- }
1170
+ return deps.topicTargetStore.getBotState().threadMode === "enabled";
1171
+ };
1172
+ const registerFollowerWithOwner = async (
1173
+ ctx: TContext,
1174
+ owner: TOwner,
1175
+ ): Promise<boolean | undefined> => {
1176
+ if (!(await refreshFollowerState())) return undefined;
1125
1177
  return deps.registerFollowerWithLeader(ctx, owner);
1126
1178
  };
1179
+ const restoreFollowerWithOwner = async (
1180
+ ctx: TContext,
1181
+ owner: TOwner,
1182
+ ): Promise<boolean | undefined> => {
1183
+ if (!(await refreshFollowerState())) return undefined;
1184
+ if (!deps.hasRememberedWorkspaceBinding?.(ctx)) return undefined;
1185
+ return deps.restoreFollowerWithLeader?.(ctx, owner);
1186
+ };
1127
1187
  return {
1128
1188
  startPolling,
1129
1189
  stopPolling,
1130
1190
  registerFollowerWithOwner,
1191
+ restoreFollowerWithOwner,
1131
1192
  stopFollowerRegistration: deps.stopFollowerRegistration,
1132
1193
  };
1133
1194
  }
@@ -1179,9 +1240,9 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
1179
1240
  let consecutiveDisabledProbes = 0;
1180
1241
  const stop = (): void => {
1181
1242
  generation += 1;
1182
- deps.lifecycle?.invalidate();
1183
1243
  if (interval) clearInterval(interval);
1184
1244
  interval = undefined;
1245
+ deps.lifecycle?.invalidate();
1185
1246
  };
1186
1247
  const check = (ctx: TContext): void => {
1187
1248
  if (transitionPromise || !canProbeTelegramThreadCapability(ctx, deps)) {
@@ -1276,8 +1337,19 @@ export function createTelegramThreadCapabilityMonitor<TContext>(
1276
1337
  return {
1277
1338
  start(ctx) {
1278
1339
  stop();
1340
+ const expectedGeneration = generation;
1279
1341
  interval = setInterval(() => {
1280
- check(ctx);
1342
+ if (generation !== expectedGeneration) return;
1343
+ try {
1344
+ check(ctx);
1345
+ } catch (error) {
1346
+ try { stop(); } catch { /* Timer shutdown must not escape the callback. */ }
1347
+ try {
1348
+ deps.recordEvent("bus", error, { phase: "capability-monitor" });
1349
+ } catch {
1350
+ // Monitor diagnostics cannot create an uncaught interval exception.
1351
+ }
1352
+ }
1281
1353
  }, intervalMs);
1282
1354
  interval.unref?.();
1283
1355
  },
@@ -1416,7 +1488,6 @@ export interface TelegramPollLoopDeps<
1416
1488
  getAcceptedThroughUpdateId?: () => number | undefined;
1417
1489
  getJournalEntryCount: () => number;
1418
1490
  signalUpdateWorker: () => void;
1419
- prepareUpdateBatch?: (updates: readonly TUpdate[]) => void;
1420
1491
  onErrorStatus: (message: string) => void;
1421
1492
  onStatusReset: () => void;
1422
1493
  sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
@@ -1446,7 +1517,6 @@ export interface TelegramPollLoopRunnerDeps<
1446
1517
  getAcceptedThroughUpdateId?: () => number | undefined;
1447
1518
  getJournalEntryCount: () => number;
1448
1519
  signalUpdateWorker: () => void;
1449
- prepareUpdateBatch?: (updates: readonly TUpdate[]) => void;
1450
1520
  updateStatus: (ctx: TContext, message?: string) => void;
1451
1521
  sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
1452
1522
  onPhaseChange?: (
@@ -1501,7 +1571,6 @@ export function createTelegramPollLoopRunner<
1501
1571
  getAcceptedThroughUpdateId: deps.getAcceptedThroughUpdateId,
1502
1572
  getJournalEntryCount: deps.getJournalEntryCount,
1503
1573
  signalUpdateWorker: deps.signalUpdateWorker,
1504
- prepareUpdateBatch: deps.prepareUpdateBatch,
1505
1574
  onErrorStatus: (message) => {
1506
1575
  updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
1507
1576
  message,
@@ -1702,7 +1771,6 @@ export async function runTelegramPollLoop<
1702
1771
  reportTelegramPollingPhase(deps, "long-poll");
1703
1772
  const updates = await requestTelegramUpdatesWithinBudget(deps, request);
1704
1773
  reportTelegramPollingResponse(deps, updates.length);
1705
- deps.prepareUpdateBatch?.(updates);
1706
1774
  consecutiveGetUpdatesConflicts = 0;
1707
1775
  currentUpdateId = updates[0]?.update_id;
1708
1776
  await admitTelegramPollingUpdateBatch({
@@ -14,7 +14,7 @@ export const TELEGRAM_DISCONNECTED_CONTEXT_MESSAGE =
14
14
 
15
15
  const LOCAL_SYSTEM_PROMPT_SUFFIX = `
16
16
 
17
- ${TELEGRAM_CONNECTED_CONTEXT_MESSAGE} For Telegram work, consult bundled Skills in routing order: \`telegram-bridge\` for the transport and turn protocol, \`generated-control-surface\` when contextual controls materially shorten feedback, then \`generative-apps\` when the interaction warrants a reusable deterministic app. Load a Skill only if its instructions are not already present in the current context. Do not use Telegram-specific features from unrelated local/TUI prompts.`;
17
+ ${TELEGRAM_CONNECTED_CONTEXT_MESSAGE} For Telegram work, consult bundled Skills in routing order: \`telegram-bridge\` for the transport and turn protocol, \`show-me\` when a user needs a truthful visual explanation of work or behavior, \`generated-control-surface\` when contextual controls materially shorten feedback, then \`generative-apps\` when the interaction warrants a reusable deterministic app. Load a Skill only if its instructions are not already present in the current context. Do not use Telegram-specific features from unrelated local/TUI prompts.`;
18
18
 
19
19
  const TELEGRAM_TURN_SYSTEM_PROMPT_SUFFIX = `
20
20
 
@@ -28,9 +28,10 @@ export const TELEGRAM_ATTACH_PROMPT_GUIDELINES = [
28
28
  "For an explicit thread target, provide chat_id plus thread_id; registered multi-instance followers default to their assigned thread target.",
29
29
  ] as const;
30
30
  export const TELEGRAM_MESSAGE_PROMPT_SNIPPET =
31
- "Send direct Telegram Markdown text when the user explicitly asks for Telegram delivery outside the normal reply flow.";
31
+ "Send direct Telegram Markdown text when the user explicitly asks for Telegram delivery to a concrete chat, channel, or live Pi Thread outside the normal reply flow.";
32
32
  export const TELEGRAM_MESSAGE_PROMPT_GUIDELINES = [
33
33
  "Use telegram_message only when the user explicitly asks to send a message to Telegram from the local/TUI side, or names a concrete Telegram delivery target.",
34
+ "For an explicitly requested channel post, pass its exact numeric id or public @username as chat_id; no local channel registry is required, and Telegram remains the authority on the bot's posting permission.",
34
35
  "For a live Pi thread target, provide thread as its case-insensitive name or numeric id; the bridge sends visibly and admits one attributed turn to that live instance. Unknown, ambiguous, same, or offline targets fail before sending.",
35
36
  "Add buttons by embedding the same top-level telegram_button HTML comments used in normal Telegram replies; Telegram does not support standalone buttons.",
36
37
  "During an active Telegram turn, omit telegram_message for the current target and answer normally; use thread only when the user requests delivery to a different live Pi thread.",
@@ -48,6 +49,8 @@ const TELEGRAM_TOOL_METADATA_LINES = Object.fromEntries(
48
49
  const TELEGRAM_MODEL_CONTEXT_TOOL_NAMES = new Set([
49
50
  "telegram_attach",
50
51
  "telegram_bind",
52
+ "telegram_channel_post",
53
+ "telegram_channel_posts",
51
54
  "telegram_message",
52
55
  ]);
53
56
  const TELEGRAM_MODEL_CONTEXT_MEMORY_KEY = Symbol.for(
@@ -1527,7 +1527,7 @@ export interface TelegramAgentEndRuntimeDeps<
1527
1527
  chatId: number,
1528
1528
  replyToMessageId: number,
1529
1529
  text: string,
1530
- options?: { target?: TelegramQueueTarget },
1530
+ options?: { target?: TelegramQueueTarget; parseMode?: "HTML" },
1531
1531
  ) => Promise<unknown>;
1532
1532
  sendQueuedAttachments: (
1533
1533
  turn: TTurn,
@@ -1908,23 +1908,37 @@ export async function handleTelegramAgentEndRuntime<
1908
1908
  const deliverActiveTurn = async () => {
1909
1909
  await deps.waitForActivityIdle?.();
1910
1910
  if (!isDeliveryActive()) return;
1911
- if (endPlan.shouldClearPreview || (!finalText && hasOutboundArtifacts)) {
1911
+ let previewCleared = false;
1912
+ const clearTurnPreview = async () => {
1913
+ if (previewCleared) return;
1912
1914
  await clearPreview?.();
1915
+ previewCleared = true;
1916
+ };
1917
+ if (endPlan.shouldClearPreview || (!finalText && hasOutboundArtifacts)) {
1918
+ await clearTurnPreview();
1913
1919
  if (!isDeliveryActive()) return;
1914
1920
  }
1915
1921
  if (endPlan.shouldSendErrorMessage) {
1922
+ const errorMessage = assistant.errorMessage ||
1923
+ "Telegram bridge: Pi failed while processing the request.";
1924
+ const isOperationAborted = errorMessage.trim().replace(/\.$/, "") ===
1925
+ "This operation was aborted";
1916
1926
  await deps.sendTextReply(
1917
1927
  turn.chatId,
1918
1928
  turn.replyToMessageId,
1919
- assistant.errorMessage ||
1920
- "Telegram bridge: Pi failed while processing the request.",
1921
- { target: turn.target },
1929
+ isOperationAborted
1930
+ ? "<b>⏹️ This operation was aborted.</b>"
1931
+ : errorMessage,
1932
+ {
1933
+ target: turn.target,
1934
+ ...(isOperationAborted ? { parseMode: "HTML" as const } : {}),
1935
+ },
1922
1936
  );
1923
1937
  if (!isDeliveryActive()) return;
1924
1938
  if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
1925
1939
  return;
1926
1940
  }
1927
- if (finalText) setPreviewPendingText(finalText);
1941
+ if (finalText && turn.queuedAttachments.length === 0) setPreviewPendingText(finalText);
1928
1942
 
1929
1943
  if (!isDeliveryActive()) return;
1930
1944
  let richAttachmentDelivered = false;
@@ -1942,7 +1956,7 @@ export async function handleTelegramAgentEndRuntime<
1942
1956
  );
1943
1957
  if (!isDeliveryActive()) return;
1944
1958
  if (richAttachmentDelivered) {
1945
- await clearPreview?.();
1959
+ await clearTurnPreview();
1946
1960
  if (!isDeliveryActive()) return;
1947
1961
  setPreviewPendingText("");
1948
1962
  }
@@ -1957,24 +1971,42 @@ export async function handleTelegramAgentEndRuntime<
1957
1971
  }
1958
1972
  }
1959
1973
  if (!isDeliveryActive()) return;
1974
+ let queuedAttachmentsDelivered = false;
1975
+ if (!richAttachmentDelivered && turn.queuedAttachments.length > 0) {
1976
+ await clearTurnPreview();
1977
+ if (!isDeliveryActive()) return;
1978
+ setPreviewPendingText("");
1979
+ await deps.sendQueuedAttachments(turn, { isDeliveryActive });
1980
+ if (!isDeliveryActive()) return;
1981
+ queuedAttachmentsDelivered = true;
1982
+ }
1960
1983
  if (!richAttachmentDelivered && endPlan.kind === "text" && finalText) {
1961
1984
  try {
1962
- const finalized = await finalizeMarkdownPreview(
1963
- turn.chatId,
1964
- finalText,
1965
- turn.replyToMessageId,
1966
- { replyMarkup, target: turn.target },
1967
- );
1968
- if (!isDeliveryActive()) return;
1969
- if (!finalized) {
1970
- await clearPreview?.();
1971
- if (!isDeliveryActive()) return;
1985
+ if (queuedAttachmentsDelivered) {
1972
1986
  await deps.sendMarkdownReply(
1973
1987
  turn.chatId,
1974
1988
  turn.replyToMessageId,
1975
1989
  finalText,
1976
1990
  { replyMarkup, target: turn.target },
1977
1991
  );
1992
+ } else {
1993
+ const finalized = await finalizeMarkdownPreview(
1994
+ turn.chatId,
1995
+ finalText,
1996
+ turn.replyToMessageId,
1997
+ { replyMarkup, target: turn.target },
1998
+ );
1999
+ if (!isDeliveryActive()) return;
2000
+ if (!finalized) {
2001
+ await clearTurnPreview();
2002
+ if (!isDeliveryActive()) return;
2003
+ await deps.sendMarkdownReply(
2004
+ turn.chatId,
2005
+ turn.replyToMessageId,
2006
+ finalText,
2007
+ { replyMarkup, target: turn.target },
2008
+ );
2009
+ }
1978
2010
  }
1979
2011
  if (!isDeliveryActive()) return;
1980
2012
  setPreviewPendingText("");
@@ -2041,7 +2073,9 @@ export async function handleTelegramAgentEndRuntime<
2041
2073
  );
2042
2074
  }
2043
2075
  if (!isDeliveryActive()) return;
2044
- if (!richAttachmentDelivered) await deps.sendQueuedAttachments(turn, { isDeliveryActive });
2076
+ if (!richAttachmentDelivered && !queuedAttachmentsDelivered) {
2077
+ await deps.sendQueuedAttachments(turn, { isDeliveryActive });
2078
+ }
2045
2079
  if (!isDeliveryActive()) return;
2046
2080
  if (endPlan.shouldDispatchNext) deps.dispatchNextQueuedTelegramTurn();
2047
2081
  };
@@ -672,7 +672,7 @@ export async function sendTelegramNativeMarkdownReply<TReplyMarkup = unknown>(
672
672
  chatId, index === 0 ? replyToMessageId : undefined, options?.target,
673
673
  (replyParameters) => deps.sendRichMessage({
674
674
  chat_id: chatId,
675
- rich_message: { markdown: chunk, skip_entity_detection: true },
675
+ rich_message: { markdown: chunk },
676
676
  reply_markup:
677
677
  index === chunks.length - 1 ? options?.replyMarkup : undefined,
678
678
  ...(replyParameters ? { reply_parameters: replyParameters } : {}),
@@ -894,7 +894,7 @@ export function createGuestMarkdownReplySender(deps: {
894
894
  return async (guestQueryId: string, markdown: string) => {
895
895
  const [richMarkdown = markdown] = splitTelegramNativeMarkdown(markdown);
896
896
  await deps.answerGuestQuery(guestQueryId, undefined, {
897
- richMessage: { markdown: richMarkdown, skip_entity_detection: true },
897
+ richMessage: { markdown: richMarkdown },
898
898
  });
899
899
  };
900
900
  }