@llblab/pi-telegram 0.52.2 → 0.53.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.
Files changed (204) hide show
  1. package/AGENTS.md +9 -7
  2. package/BACKLOG.md +1 -18
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +7 -11
  5. package/api/commands.ts +3 -0
  6. package/dist/api/commands.d.ts +1 -1
  7. package/dist/lib/activity-verbosity.d.ts +1 -5
  8. package/dist/lib/activity-verbosity.js +15 -42
  9. package/dist/lib/activity.d.ts +9 -0
  10. package/dist/lib/activity.js +39 -10
  11. package/dist/lib/agent-messages.js +7 -3
  12. package/dist/lib/bindings.d.ts +43 -5
  13. package/dist/lib/bindings.js +163 -20
  14. package/dist/lib/bus-api.d.ts +12 -0
  15. package/dist/lib/bus-api.js +182 -79
  16. package/dist/lib/bus-follower.d.ts +111 -31
  17. package/dist/lib/bus-follower.js +1324 -359
  18. package/dist/lib/bus-leader.d.ts +41 -91
  19. package/dist/lib/bus-leader.js +969 -637
  20. package/dist/lib/bus-transport.js +16 -9
  21. package/dist/lib/bus.d.ts +215 -18
  22. package/dist/lib/bus.js +1048 -327
  23. package/dist/lib/channel-posts.d.ts +5 -0
  24. package/dist/lib/channel-posts.js +434 -261
  25. package/dist/lib/command-templates.d.ts +26 -5
  26. package/dist/lib/command-templates.js +92 -176
  27. package/dist/lib/commands.d.ts +168 -82
  28. package/dist/lib/commands.js +1409 -291
  29. package/dist/lib/config.d.ts +1 -8
  30. package/dist/lib/config.js +34 -76
  31. package/dist/lib/delivery.d.ts +2 -2
  32. package/dist/lib/delivery.js +16 -16
  33. package/dist/lib/extension.js +639 -171
  34. package/dist/lib/generative-app-worker.mjs +19 -7
  35. package/dist/lib/generative-apps.d.ts +0 -5
  36. package/dist/lib/generative-apps.js +61 -49
  37. package/dist/lib/inbound.d.ts +5 -0
  38. package/dist/lib/inbound.js +35 -59
  39. package/dist/lib/journal.d.ts +33 -3
  40. package/dist/lib/journal.js +2063 -782
  41. package/dist/lib/lifecycle.d.ts +2 -6
  42. package/dist/lib/lifecycle.js +75 -58
  43. package/dist/lib/locks.d.ts +29 -3
  44. package/dist/lib/locks.js +289 -108
  45. package/dist/lib/logging.js +50 -14
  46. package/dist/lib/media.d.ts +22 -1
  47. package/dist/lib/media.js +46 -28
  48. package/dist/lib/menu-model.d.ts +19 -5
  49. package/dist/lib/menu-model.js +53 -11
  50. package/dist/lib/menu-queue.d.ts +5 -4
  51. package/dist/lib/menu-queue.js +45 -63
  52. package/dist/lib/menu-settings.d.ts +5 -4
  53. package/dist/lib/menu-settings.js +114 -78
  54. package/dist/lib/menu-status.d.ts +1 -1
  55. package/dist/lib/menu-status.js +8 -25
  56. package/dist/lib/menu-thinking.d.ts +6 -1
  57. package/dist/lib/menu-thinking.js +14 -21
  58. package/dist/lib/menu.d.ts +8 -61
  59. package/dist/lib/menu.js +92 -164
  60. package/dist/lib/model.d.ts +1 -1
  61. package/dist/lib/model.js +2 -16
  62. package/dist/lib/outbound-attachments.d.ts +6 -27
  63. package/dist/lib/outbound-attachments.js +45 -38
  64. package/dist/lib/outbound-buttons.js +22 -16
  65. package/dist/lib/outbound-markup.d.ts +0 -2
  66. package/dist/lib/outbound-markup.js +51 -64
  67. package/dist/lib/outbound-voice.js +6 -3
  68. package/dist/lib/outbound.d.ts +6 -6
  69. package/dist/lib/outbound.js +65 -51
  70. package/dist/lib/ownership.js +6 -2
  71. package/dist/lib/paths.d.ts +5 -0
  72. package/dist/lib/paths.js +37 -21
  73. package/dist/lib/pi.d.ts +2 -0
  74. package/dist/lib/pi.js +8 -1
  75. package/dist/lib/polling.d.ts +21 -37
  76. package/dist/lib/polling.js +59 -50
  77. package/dist/lib/preview.js +39 -19
  78. package/dist/lib/process-identity.d.ts +24 -0
  79. package/dist/lib/process-identity.js +133 -0
  80. package/dist/lib/prompts.js +2 -7
  81. package/dist/lib/queue.d.ts +14 -16
  82. package/dist/lib/queue.js +252 -127
  83. package/dist/lib/recovery.d.ts +2 -83
  84. package/dist/lib/recovery.js +20 -274
  85. package/dist/lib/replies.d.ts +16 -24
  86. package/dist/lib/replies.js +99 -116
  87. package/dist/lib/routing.d.ts +120 -7
  88. package/dist/lib/routing.js +4667 -1152
  89. package/dist/lib/runtime.d.ts +1 -11
  90. package/dist/lib/sections.d.ts +4 -29
  91. package/dist/lib/sections.js +7 -20
  92. package/dist/lib/skills.js +4 -3
  93. package/dist/lib/status.js +86 -57
  94. package/dist/lib/sync.d.ts +1 -3
  95. package/dist/lib/sync.js +67 -43
  96. package/dist/lib/target.d.ts +4 -0
  97. package/dist/lib/target.js +18 -0
  98. package/dist/lib/telegram-api.d.ts +18 -13
  99. package/dist/lib/telegram-api.js +148 -53
  100. package/dist/lib/text-groups.js +4 -39
  101. package/dist/lib/thread-cleanup-manager.d.ts +6 -27
  102. package/dist/lib/thread-cleanup-manager.js +369 -186
  103. package/dist/lib/thread-display.d.ts +1 -2
  104. package/dist/lib/thread-display.js +58 -38
  105. package/dist/lib/thread-naming.d.ts +28 -8
  106. package/dist/lib/thread-naming.js +175 -66
  107. package/dist/lib/thread-reconciler.d.ts +42 -16
  108. package/dist/lib/thread-reconciler.js +90 -50
  109. package/dist/lib/threads.d.ts +82 -20
  110. package/dist/lib/threads.js +2247 -855
  111. package/dist/lib/time-injection.js +1 -2
  112. package/dist/lib/turns.js +1 -19
  113. package/dist/lib/updates.d.ts +169 -80
  114. package/dist/lib/updates.js +2418 -641
  115. package/dist/lib/voice.d.ts +7 -22
  116. package/dist/lib/voice.js +7 -22
  117. package/dist/lib/wire.js +1 -1
  118. package/dist/lib/workspace-admission.d.ts +3 -1
  119. package/dist/lib/workspace-admission.js +425 -211
  120. package/dist/lib/workspace-identity.js +8 -13
  121. package/dist/lib/workspace-retirement.d.ts +1 -1
  122. package/dist/lib/workspace-retirement.js +328 -140
  123. package/dist/lib/workspace-slots.js +8 -4
  124. package/dist/package.json +1 -1
  125. package/docs/architecture.md +31 -29
  126. package/docs/inbound.md +1 -1
  127. package/docs/multi-instance-bus.md +15 -3
  128. package/docs/outbound.md +1 -1
  129. package/docs/public-api.md +29 -1
  130. package/docs/ui-style.md +25 -8
  131. package/lib/activity-verbosity.ts +59 -82
  132. package/lib/activity.ts +146 -102
  133. package/lib/agent-messages.ts +12 -5
  134. package/lib/bindings.ts +389 -82
  135. package/lib/bus-api.ts +263 -102
  136. package/lib/bus-follower.ts +2440 -601
  137. package/lib/bus-leader.ts +2209 -1290
  138. package/lib/bus-transport.ts +46 -12
  139. package/lib/bus.ts +1673 -444
  140. package/lib/channel-posts.ts +848 -327
  141. package/lib/command-templates.ts +152 -265
  142. package/lib/commands.ts +2398 -555
  143. package/lib/config.ts +177 -170
  144. package/lib/delivery.ts +41 -29
  145. package/lib/extension.ts +1206 -433
  146. package/lib/generative-app-worker.mjs +19 -7
  147. package/lib/generative-apps.ts +324 -211
  148. package/lib/inbound.ts +95 -97
  149. package/lib/journal.ts +5311 -1763
  150. package/lib/keyboard.ts +1 -3
  151. package/lib/lifecycle.ts +128 -78
  152. package/lib/locks.ts +677 -203
  153. package/lib/logging.ts +147 -58
  154. package/lib/media.ts +82 -43
  155. package/lib/menu-model.ts +85 -18
  156. package/lib/menu-queue.ts +88 -129
  157. package/lib/menu-settings.ts +260 -155
  158. package/lib/menu-status.ts +20 -60
  159. package/lib/menu-thinking.ts +41 -56
  160. package/lib/menu.ts +112 -285
  161. package/lib/model.ts +8 -22
  162. package/lib/outbound-attachments.ts +156 -125
  163. package/lib/outbound-buttons.ts +76 -49
  164. package/lib/outbound-markup.ts +96 -94
  165. package/lib/outbound-voice.ts +33 -27
  166. package/lib/outbound.ts +128 -125
  167. package/lib/ownership.ts +19 -10
  168. package/lib/paths.ts +148 -49
  169. package/lib/pi.ts +35 -15
  170. package/lib/polling.ts +144 -147
  171. package/lib/preview.ts +93 -40
  172. package/lib/process-identity.ts +164 -0
  173. package/lib/prompts.ts +4 -11
  174. package/lib/queue.ts +516 -301
  175. package/lib/recovery.ts +50 -454
  176. package/lib/rendering.ts +1 -3
  177. package/lib/replies.ts +248 -209
  178. package/lib/routing.ts +8509 -2186
  179. package/lib/runtime.ts +1 -12
  180. package/lib/sections.ts +24 -89
  181. package/lib/setup.ts +1 -3
  182. package/lib/skills.ts +9 -4
  183. package/lib/status.ts +215 -110
  184. package/lib/sync.ts +448 -361
  185. package/lib/target.ts +22 -3
  186. package/lib/telegram-api.ts +302 -127
  187. package/lib/text-groups.ts +5 -39
  188. package/lib/thread-cleanup-manager.ts +833 -331
  189. package/lib/thread-display.ts +213 -97
  190. package/lib/thread-naming.ts +232 -87
  191. package/lib/thread-reconciler.ts +155 -72
  192. package/lib/threads.ts +5318 -1845
  193. package/lib/time-injection.ts +1 -4
  194. package/lib/turns.ts +15 -28
  195. package/lib/updates.ts +5184 -1612
  196. package/lib/voice.ts +7 -35
  197. package/lib/wire.ts +1 -1
  198. package/lib/workspace-admission.ts +996 -416
  199. package/lib/workspace-identity.ts +14 -17
  200. package/lib/workspace-retirement.ts +878 -376
  201. package/lib/workspace-slots.ts +22 -13
  202. package/package.json +1 -1
  203. package/scripts/check-downgrade.mjs +0 -472
  204. package/scripts/measure-workspace.mjs +0 -108
package/dist/lib/sync.js CHANGED
@@ -3,13 +3,13 @@
3
3
  * Zones: Telegram bot reality mirror, demand-driven reconciliation, status diagnostics
4
4
  * Owns pure contracts for deciding when local Telegram mirror state should be refreshed without querying Telegram on every action
5
5
  */
6
- import { getTelegramApiErrorRequestTarget, isTelegramStaleTargetHttpError } from "./telegram-api.js";
7
6
  import { getTelegramTargetKey } from "./target.js";
7
+ import { getTelegramApiErrorRequestTarget, isTelegramStaleTargetHttpError, } from "./telegram-api.js";
8
8
  import * as ThreadReconciler from "./thread-reconciler.js";
9
- import { TelegramWorkspaceSlotUnavailableError } from "./workspace-slots.js";
10
- import { normalizeTelegramWorkspacePath } from "./workspace-identity.js";
9
+ import { commitTelegramWorkspaceProvisionBinding, createTelegramCleanupTargetProtection, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError, provisionOwnBusTopic, } from "./threads.js";
11
10
  import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
12
- import { createTelegramCleanupTargetProtection, commitTelegramWorkspaceProvisionBinding, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError, provisionOwnBusTopic, } from "./threads.js";
11
+ import { normalizeTelegramWorkspacePath } from "./workspace-identity.js";
12
+ import { TelegramWorkspaceSlotUnavailableError } from "./workspace-slots.js";
13
13
  function markTelegramConfigSyncChange(state, action, options) {
14
14
  const nowMs = options?.nowMs ?? Date.now();
15
15
  let nextState = markTelegramSyncSliceFresh(state, "pairing", {
@@ -32,10 +32,13 @@ export function createTelegramPreservedLeaderQuitHandler(deps) {
32
32
  const profile = deps.getProfileName();
33
33
  if (epoch === undefined || !isSessionCurrent())
34
34
  return undefined;
35
- const isCurrent = () => isSessionCurrent() && deps.getCurrentLeaderEpoch() === epoch &&
36
- deps.getProfileName() === profile && deps.isPollingSuspended();
35
+ const isCurrent = () => isSessionCurrent() &&
36
+ deps.getCurrentLeaderEpoch() === epoch &&
37
+ deps.getProfileName() === profile &&
38
+ deps.isPollingSuspended();
37
39
  return async () => {
38
- if (!isCurrent() || await deps.resolveAutomaticThreadCleanupEnabled() !== false)
40
+ if (!isCurrent() ||
41
+ (await deps.resolveAutomaticThreadCleanupEnabled()) !== false)
39
42
  return;
40
43
  if (!isCurrent())
41
44
  return;
@@ -49,13 +52,20 @@ export function createTelegramPreservedLeaderQuitHandler(deps) {
49
52
  await deps.topicTargetStore.load();
50
53
  if (!isCurrent())
51
54
  return;
52
- const records = deps.topicTargetStore.list().filter((record) => record.instanceId === deps.instanceId && (record.status === "active" || record.status === "starting"));
55
+ const records = deps.topicTargetStore
56
+ .list()
57
+ .filter((record) => record.instanceId === deps.instanceId &&
58
+ (record.status === "active" || record.status === "starting"));
53
59
  if (records.length !== 1)
54
60
  return;
55
61
  const record = records[0];
56
- if (deps.topicTargetStore.listPendingCleanups().some((intent) => intent.target.chatId === record.target.chatId && intent.target.threadId === record.target.threadId))
62
+ if (deps.topicTargetStore
63
+ .listPendingCleanups()
64
+ .some((intent) => intent.target.chatId === record.target.chatId &&
65
+ intent.target.threadId === record.target.threadId))
57
66
  return;
58
- if (!await deps.topicTargetStore.detachTargetOwner(record, isCurrent) && isCurrent()) {
67
+ if (!(await deps.topicTargetStore.detachTargetOwner(record, isCurrent)) &&
68
+ isCurrent()) {
59
69
  throw new Error("Telegram preserved leader detachment was not committed.");
60
70
  }
61
71
  });
@@ -93,16 +103,22 @@ export function createTelegramThreadDisconnectAssembly(deps) {
93
103
  catch (error) {
94
104
  // Never retry a stop whose teardown/release outcome may already have committed.
95
105
  if (stopAttempted) {
96
- deps.recordRuntimeEvent("connection", error, { phase: "disconnect-stop" });
106
+ deps.recordRuntimeEvent("connection", error, {
107
+ phase: "disconnect-stop",
108
+ });
97
109
  throw error;
98
110
  }
99
- deps.recordRuntimeEvent("connection", error, { phase: "disconnect-cleanup" });
111
+ deps.recordRuntimeEvent("connection", error, {
112
+ phase: "disconnect-cleanup",
113
+ });
100
114
  let stopped;
101
115
  try {
102
116
  stopped = await stop();
103
117
  }
104
118
  catch (stopError) {
105
- deps.recordRuntimeEvent("connection", stopError, { phase: "disconnect-stop" });
119
+ deps.recordRuntimeEvent("connection", stopError, {
120
+ phase: "disconnect-stop",
121
+ });
106
122
  throw stopError;
107
123
  }
108
124
  const separator = /[.!?]$/u.test(stopped) ? " " : ". ";
@@ -153,8 +169,11 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
153
169
  target,
154
170
  requestedAtMs: (deps.getNowMs ?? Date.now)(),
155
171
  };
156
- const departingRecord = deps.topicTargetStore.list().find((record) => record.instanceId === currentRecord.instanceId &&
157
- record.target.chatId === target.chatId && record.target.threadId === target.threadId);
172
+ const departingRecord = deps.topicTargetStore
173
+ .list()
174
+ .find((record) => record.instanceId === currentRecord.instanceId &&
175
+ record.target.chatId === target.chatId &&
176
+ record.target.threadId === target.threadId);
158
177
  const isCleanupTargetProtected = createTelegramCleanupTargetProtection(deps.topicTargetStore, departingRecord);
159
178
  deps.topicTargetStore.upsertPendingCleanup(intent);
160
179
  await deps.topicTargetStore.persist();
@@ -312,23 +331,32 @@ export function captureTelegramStaleTargetRequestRecovery(body, deps) {
312
331
  if (!target || epoch === undefined)
313
332
  return undefined;
314
333
  const key = getTelegramTargetKey(target);
315
- const record = deps.topicTargetStore.list().find((candidate) => getTelegramTargetKey(candidate.target) === key);
334
+ const record = deps.topicTargetStore
335
+ .list()
336
+ .find((candidate) => getTelegramTargetKey(candidate.target) === key);
316
337
  if (!record)
317
338
  return undefined;
318
339
  const generation = deps.getSessionGeneration();
319
340
  const profile = deps.getProfileName();
320
341
  const isAuthorityCurrent = () => deps.getCurrentLeaderEpoch() === epoch &&
321
- deps.getSessionGeneration() === generation && deps.getProfileName() === profile;
342
+ deps.getSessionGeneration() === generation &&
343
+ deps.getProfileName() === profile;
322
344
  const isCurrent = () => {
323
- const current = deps.topicTargetStore.list().find((candidate) => getTelegramTargetKey(candidate.target) === key);
324
- return isAuthorityCurrent() &&
325
- current?.instanceId === record.instanceId && current?.profileKey === record.profileKey &&
326
- current?.updatedAtMs === record.updatedAtMs && current?.createdAtMs === record.createdAtMs;
345
+ const current = deps.topicTargetStore
346
+ .list()
347
+ .find((candidate) => getTelegramTargetKey(candidate.target) === key);
348
+ return (isAuthorityCurrent() &&
349
+ current?.instanceId === record.instanceId &&
350
+ current?.profileKey === record.profileKey &&
351
+ current?.updatedAtMs === record.updatedAtMs &&
352
+ current?.createdAtMs === record.createdAtMs);
327
353
  };
328
354
  return async (error) => {
329
355
  const requestTarget = getTelegramApiErrorRequestTarget(error);
330
- if (!isTelegramStaleTargetHttpError(error) || !requestTarget ||
331
- getTelegramTargetKey(requestTarget) !== key || !isCurrent())
356
+ if (!isTelegramStaleTargetHttpError(error) ||
357
+ !requestTarget ||
358
+ getTelegramTargetKey(requestTarget) !== key ||
359
+ !isCurrent())
332
360
  return;
333
361
  if (await recoverStaleTelegramTopicApiError({ chat_id: target.chatId, message_thread_id: target.threadId }, error, { ...deps, isCurrent, isAuthorityCurrent })) {
334
362
  deps.onRecovered();
@@ -354,7 +382,7 @@ export async function recoverStaleTelegramTopicApiError(apiBody, error, deps) {
354
382
  const recover = async () => {
355
383
  if (deps.isCurrent) {
356
384
  if (!deps.topicTargetStore.invalidateTarget ||
357
- !await deps.topicTargetStore.invalidateTarget(target, deps.isCurrent, String(error)))
385
+ !(await deps.topicTargetStore.invalidateTarget(target, deps.isCurrent, String(error))))
358
386
  return false;
359
387
  if (deps.isAuthorityCurrent?.() === false)
360
388
  return false;
@@ -436,7 +464,12 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
436
464
  const legacyLeaderRecord = deps.topicTargetStore.getByProfileKey(leaderProfileKey);
437
465
  let capacityUnavailable = false;
438
466
  const workspaceIdentity = normalizedLeaderCwd
439
- ? deps.topicTargetStore.claimWorkspaceIdentity(normalizedLeaderCwd, deps.instanceId, legacyLeaderRecord?.instanceId, { sessionId: deps.sessionId, onCapacityUnavailable() { capacityUnavailable = true; } })
467
+ ? deps.topicTargetStore.claimWorkspaceIdentity(normalizedLeaderCwd, deps.instanceId, legacyLeaderRecord?.instanceId, {
468
+ sessionId: deps.sessionId,
469
+ onCapacityUnavailable() {
470
+ capacityUnavailable = true;
471
+ },
472
+ })
440
473
  : undefined;
441
474
  if (deps.cwd && !workspaceIdentity) {
442
475
  if (capacityUnavailable)
@@ -471,7 +504,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
471
504
  target: { ...committed.target },
472
505
  slot: committed.slot ?? result.slot,
473
506
  ...(committed.threadName ? { threadName: committed.threadName } : {}),
474
- ...(committed.displayTitle ? { displayTitle: committed.displayTitle } : {}),
507
+ ...(committed.displayTitle
508
+ ? { displayTitle: committed.displayTitle }
509
+ : {}),
475
510
  };
476
511
  };
477
512
  try {
@@ -512,7 +547,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
512
547
  ...(legacyLeaderRecord.threadName
513
548
  ? { threadName: legacyLeaderRecord.threadName }
514
549
  : {}),
515
- ...(legacyLeaderRecord.slot ? { slot: legacyLeaderRecord.slot } : {}),
550
+ ...(legacyLeaderRecord.slot
551
+ ? { slot: legacyLeaderRecord.slot }
552
+ : {}),
516
553
  updatedAtMs: legacyLeaderRecord.updatedAtMs,
517
554
  }
518
555
  : undefined;
@@ -600,7 +637,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
600
637
  });
601
638
  // Legacy callers without session identity may reuse the active instance target.
602
639
  // Session-aware callers must resolve their exact Workspace binding instead.
603
- if (deps.sessionId === undefined && !deps.forceFreshUnnamed && priorTargets.length > 0) {
640
+ if (deps.sessionId === undefined &&
641
+ !deps.forceFreshUnnamed &&
642
+ priorTargets.length > 0) {
604
643
  const record = priorTargets[0];
605
644
  deps.recordEvent("telegram", "Leader thread preserved after session lifecycle change", {
606
645
  phase: "leader-thread-reused",
@@ -747,21 +786,6 @@ export function createTelegramProvisioningActivityRuntime() {
747
786
  },
748
787
  };
749
788
  }
750
- const RECONCILE_TRIGGERS = new Set([
751
- "startup",
752
- "reload",
753
- "topic-lifecycle",
754
- "stale-api-error",
755
- "setup-change",
756
- "pairing-change",
757
- "follower-register",
758
- "follower-prune",
759
- "status-request",
760
- "leader-health-tick",
761
- ]);
762
- export function shouldReconcileTelegramSync(trigger) {
763
- return RECONCILE_TRIGGERS.has(trigger);
764
- }
765
789
  export function markTelegramSyncSliceSuspect(state, slice, input) {
766
790
  return {
767
791
  ...state,
@@ -18,3 +18,7 @@ export declare function areTelegramTargetsEqual(left: TelegramTarget, right: Tel
18
18
  export declare function getTelegramTargetThreadParams(target: TelegramTarget): {
19
19
  message_thread_id?: number;
20
20
  };
21
+ /** Pure wire parser for `{ chatId, threadId? }`; unknown fields are dropped and malformed shapes refuse. */
22
+ export declare function parseTelegramTarget(value: unknown): TelegramTarget | undefined;
23
+ /** Pure Bot API integer id parser accepting integer numbers or non-blank integer strings. */
24
+ export declare function parseTelegramIntegerId(value: unknown): number | undefined;
@@ -25,3 +25,21 @@ export function getTelegramTargetThreadParams(target) {
25
25
  ? { message_thread_id: target.threadId }
26
26
  : {};
27
27
  }
28
+ /** Pure wire parser for `{ chatId, threadId? }`; unknown fields are dropped and malformed shapes refuse. */
29
+ export function parseTelegramTarget(value) {
30
+ if (typeof value !== "object" || value === null || Array.isArray(value))
31
+ return undefined;
32
+ const { chatId, threadId } = value;
33
+ if (typeof chatId !== "number")
34
+ return undefined;
35
+ return typeof threadId === "number" ? { chatId, threadId } : { chatId };
36
+ }
37
+ /** Pure Bot API integer id parser accepting integer numbers or non-blank integer strings. */
38
+ export function parseTelegramIntegerId(value) {
39
+ if (typeof value === "number" && Number.isInteger(value))
40
+ return value;
41
+ if (typeof value !== "string" || value.trim() === "")
42
+ return undefined;
43
+ const parsed = Number(value);
44
+ return Number.isInteger(parsed) ? parsed : undefined;
45
+ }
@@ -7,7 +7,8 @@
7
7
  */
8
8
  import type { TelegramAttachmentSource } from "./media.ts";
9
9
  export declare const TELEGRAM_FILE_MAX_BYTES: number;
10
- export declare function getTelegramInboundFileByteLimitFromEnv(env: NodeJS.ProcessEnv, names: string[], defaultValue?: number): number;
10
+ /** First positive safe-integer byte limit among ordered env names, else the caller's default. */
11
+ export declare function getTelegramByteLimitFromEnv(env: NodeJS.ProcessEnv, names: readonly string[], defaultValue: number): number;
11
12
  export type TelegramNetworkFamilyPolicy = "auto" | "ipv4" | "ipv6" | "ipv4-fallback";
12
13
  type TelegramNetworkFamily = 4 | 6;
13
14
  export interface TelegramUser {
@@ -288,6 +289,8 @@ export interface TelegramApiRetryWait {
288
289
  retryAfterSeconds?: number;
289
290
  }
290
291
  export interface TelegramApiCallOptions {
292
+ /** Local per-call effect authority; independent of source completion and transport ownership. */
293
+ assertAuthority?: () => void;
291
294
  signal?: AbortSignal;
292
295
  maxAttempts?: number;
293
296
  retryRateLimit?: boolean;
@@ -297,6 +300,12 @@ export interface TelegramApiCallOptions {
297
300
  /** Observability hook fired before a 429 retry wait, never for 5xx waits. */
298
301
  onRetryWait?: (wait: TelegramApiRetryWait) => void;
299
302
  }
303
+ export declare class TelegramApiAuthorityError extends Error {
304
+ readonly requestIssued: boolean;
305
+ constructor(requestIssued: boolean);
306
+ }
307
+ /** Recheck caller authority, classifying refusal by whether a Bot API request was already issued. */
308
+ export declare function assertTelegramApiCallAuthority(assertAuthority: (() => void) | undefined, requestIssued: boolean): void;
300
309
  export interface TelegramFileDownloadOptions {
301
310
  signal?: AbortSignal;
302
311
  maxFileSizeBytes?: number;
@@ -338,14 +347,14 @@ export interface TelegramEditGuestInlineMessageContent {
338
347
  richMessage?: TelegramInputRichMessage;
339
348
  parseMode?: "HTML";
340
349
  }
341
- export interface TelegramAnswerCallbackQueryOptions {
350
+ export interface TelegramAnswerCallbackQueryOptions extends Pick<TelegramApiCallOptions, "assertAuthority"> {
342
351
  recordRuntimeEvent?: (kind: "api", error: unknown, details?: Record<string, unknown>) => void;
343
352
  }
344
353
  export interface TelegramApiClient {
345
354
  call: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
346
355
  callMultipart: <TResponse>(method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions) => Promise<TResponse>;
347
356
  downloadFile: (fileId: string, suggestedName: string, tempDir: string, options?: TelegramFileDownloadOptions) => Promise<string>;
348
- answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
357
+ answerCallbackQuery: (callbackQueryId: string, text?: string, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
349
358
  answerGuestQuery?: (guestQueryId: string, text?: string, options?: TelegramAnswerGuestQueryOptions) => Promise<void>;
350
359
  }
351
360
  export interface TelegramApiTargetActivityRuntime {
@@ -422,16 +431,14 @@ export interface TelegramBridgeApiRuntimeDeps {
422
431
  chatActionMinIntervalMs?: number;
423
432
  chatActionMaxGates?: number;
424
433
  }
425
- export interface TelegramBridgeApiRuntime {
426
- call: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
427
- callMultipart: <TResponse>(method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions) => Promise<TResponse>;
434
+ export interface TelegramBridgeApiRuntime extends Pick<TelegramApiClient, "call" | "callMultipart"> {
428
435
  downloadFile: (fileId: string, suggestedName: string, source?: TelegramAttachmentSource) => Promise<string>;
429
436
  deleteWebhook: (signal?: AbortSignal) => Promise<boolean>;
430
437
  getUpdates: (body: Record<string, unknown>, signal?: AbortSignal) => Promise<TelegramUpdate[]>;
431
438
  setMyCommands: (commands: readonly {
432
439
  command: string;
433
440
  description: string;
434
- }[]) => Promise<boolean>;
441
+ }[], options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<boolean>;
435
442
  sendChatAction: (chatId: number, action: string, options?: {
436
443
  message_thread_id?: number;
437
444
  }) => Promise<boolean>;
@@ -446,12 +453,12 @@ export interface TelegramBridgeApiRuntime {
446
453
  entities?: unknown[];
447
454
  message_thread_id?: number;
448
455
  }) => Promise<boolean>;
449
- sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
450
- sendRichMessage: (body: TelegramSendRichMessageBody) => Promise<TelegramSentMessage>;
456
+ sendMessage: (body: TelegramSendMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
457
+ sendRichMessage: (body: TelegramSendRichMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
451
458
  sendRichMessageDraft: (body: TelegramSendRichMessageDraftBody) => Promise<boolean>;
452
- editMessageText: (body: TelegramEditMessageTextBody) => Promise<"edited" | "unchanged">;
459
+ editMessageText: (body: TelegramEditMessageTextBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<"edited" | "unchanged">;
453
460
  editMessageReplyMarkup: (chatId: number, messageId: number, replyMarkup: unknown) => Promise<void>;
454
- answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
461
+ answerCallbackQuery: (callbackQueryId: string, text?: string, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
455
462
  answerGuestQuery: (guestQueryId: string, text?: string, options?: TelegramAnswerGuestQueryOptions) => Promise<void>;
456
463
  /**
457
464
  * Temporary Guest Mode ACK experiment: answers the guest query and returns
@@ -514,8 +521,6 @@ export declare function fetchTelegramBotIdentity(botToken: string, fetchImpl?: t
514
521
  */
515
522
  export declare function callTelegramMultipart<TResponse>(botToken: string | undefined, method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions): Promise<TResponse>;
516
523
  export declare function downloadTelegramFile(botToken: string | undefined, fileId: string, suggestedName: string, tempDir: string, options?: TelegramFileDownloadOptions): Promise<string>;
517
- /** Tooltip-like callback answers omit a terminal sentence period; ellipses and other punctuation stay literal. */
518
- export declare function formatTelegramCallbackAnswerText(text: string | undefined): string | undefined;
519
524
  export declare function answerTelegramCallbackQuery(botToken: string | undefined, callbackQueryId: string, text?: string, options?: TelegramAnswerCallbackQueryOptions): Promise<void>;
520
525
  export declare function createTelegramChatActionSender<TAction extends string>(sendChatAction: (chatId: number, action: TAction, options?: {
521
526
  message_thread_id?: number;