stitchkit 0.100.1 → 0.100.3

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 (233) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +2 -1
  3. package/dist/application/process-lifecycle-transitions.d.ts +13 -2
  4. package/dist/application/process-lifecycle-transitions.d.ts.map +1 -1
  5. package/dist/application/process-lifecycle.d.ts +2 -1
  6. package/dist/application/process-lifecycle.d.ts.map +1 -1
  7. package/dist/entrypoints/agent-runtime/browser.js +119 -119
  8. package/dist/entrypoints/agent-runtime/coding-tools.js +15 -20
  9. package/dist/entrypoints/agent-runtime/harness.js +55 -63
  10. package/dist/entrypoints/agent-runtime/openrouter.js +6 -6
  11. package/dist/entrypoints/agent-runtime/sandbox.js +20 -25
  12. package/dist/entrypoints/agent-runtime/sqlite/bun.js +11 -13
  13. package/dist/entrypoints/agent-runtime/sqlite/node.js +11 -13
  14. package/dist/entrypoints/agent-runtime/testing.js +32 -34
  15. package/dist/entrypoints/agent-runtime.js +395 -402
  16. package/dist/entrypoints/application/diagnostic-journal.js +26 -27
  17. package/dist/entrypoints/application/directory-inbox.js +16 -17
  18. package/dist/entrypoints/application/grammy.js +13 -13
  19. package/dist/entrypoints/application/opentelemetry.js +13 -13
  20. package/dist/entrypoints/application/schemas.js +24 -24
  21. package/dist/entrypoints/application.d.ts +1 -1
  22. package/dist/entrypoints/application.d.ts.map +1 -1
  23. package/dist/entrypoints/application.js +218 -214
  24. package/dist/entrypoints/bin/upgrade-cli.js +2 -2
  25. package/dist/entrypoints/cli.js +81 -84
  26. package/dist/entrypoints/contract.js +54 -57
  27. package/dist/entrypoints/declaration.js +22 -22
  28. package/dist/entrypoints/files.js +18 -21
  29. package/dist/entrypoints/geo.js +7 -8
  30. package/dist/entrypoints/google.js +2 -2
  31. package/dist/entrypoints/index.js +150 -154
  32. package/dist/entrypoints/live.js +45 -47
  33. package/dist/entrypoints/node.js +68 -74
  34. package/dist/entrypoints/oauth.js +6 -6
  35. package/dist/entrypoints/observability.d.ts +1 -0
  36. package/dist/entrypoints/observability.d.ts.map +1 -1
  37. package/dist/entrypoints/observability.js +115 -87
  38. package/dist/entrypoints/primitives.js +35 -35
  39. package/dist/entrypoints/react/keyboard.d.ts +4 -0
  40. package/dist/entrypoints/react/keyboard.d.ts.map +1 -0
  41. package/dist/entrypoints/react/keyboard.js +397 -0
  42. package/dist/entrypoints/react.js +10 -10
  43. package/dist/entrypoints/release.js +7 -7
  44. package/dist/entrypoints/remote.js +15 -17
  45. package/dist/entrypoints/server.js +198 -200
  46. package/dist/entrypoints/telegram/html.d.ts +5 -0
  47. package/dist/entrypoints/telegram/html.d.ts.map +1 -0
  48. package/dist/entrypoints/telegram/html.js +26 -0
  49. package/dist/entrypoints/telegram/screens.d.ts +14 -0
  50. package/dist/entrypoints/telegram/screens.d.ts.map +1 -0
  51. package/dist/entrypoints/telegram/screens.js +2028 -0
  52. package/dist/entrypoints/telegram.d.ts +5 -1
  53. package/dist/entrypoints/telegram.d.ts.map +1 -1
  54. package/dist/entrypoints/telegram.js +548 -156
  55. package/dist/entrypoints/testing.js +75 -79
  56. package/dist/entrypoints/tools/connections.js +30 -32
  57. package/dist/entrypoints/tools/contract.js +18 -20
  58. package/dist/entrypoints/tools/invoker.js +12 -17
  59. package/dist/entrypoints/tools.js +223 -229
  60. package/dist/entrypoints/tracking/server.js +6 -6
  61. package/dist/entrypoints/tracking.js +40 -42
  62. package/dist/entrypoints/voice.js +3 -3
  63. package/dist/{index-eaysmdtq.js → index-0ndk4555.js} +56 -56
  64. package/dist/{index-2bxbyab0.js → index-1bx1xv1k.js} +1 -1
  65. package/dist/{index-p5r7qpdd.js → index-1eeqpadm.js} +4 -4
  66. package/dist/{index-hadmp193.js → index-3ct7hhq6.js} +210 -210
  67. package/dist/{index-e01r054k.js → index-45tdvqa3.js} +26 -26
  68. package/dist/{index-dce5wxcq.js → index-4s0fep5q.js} +8 -8
  69. package/dist/index-50ejfz75.js +9 -0
  70. package/dist/{index-m9zn6cpq.js → index-5107q189.js} +12 -12
  71. package/dist/{index-sbdmyz75.js → index-5cc6r990.js} +3 -3
  72. package/dist/index-6k1svmjh.js +141 -0
  73. package/dist/{index-c6bxbsnc.js → index-7kxw1awn.js} +2 -2
  74. package/dist/{index-0r0fbk7z.js → index-7z9ym7t8.js} +28 -28
  75. package/dist/{index-p7hgdyhb.js → index-814xpr6d.js} +2 -2
  76. package/dist/{index-gtj3zc53.js → index-8kzct1d6.js} +99 -99
  77. package/dist/{index-1zbbzgfh.js → index-9pw0a51a.js} +4 -4
  78. package/dist/{index-79z7m7dt.js → index-a9pd417t.js} +35 -35
  79. package/dist/{index-ham5sz8m.js → index-aaj97r4y.js} +45 -45
  80. package/dist/{index-s812ycat.js → index-aan6hqb9.js} +3 -3
  81. package/dist/{index-10x2hbx0.js → index-adhgd4pe.js} +44 -44
  82. package/dist/{index-2gxzjvyr.js → index-afjvjyck.js} +23 -23
  83. package/dist/{index-qmy6zq75.js → index-ahvsksw5.js} +16 -16
  84. package/dist/{index-hr4tzbwq.js → index-ap3gqe56.js} +3 -3
  85. package/dist/{index-znfs20ss.js → index-b2bz0kdm.js} +16 -16
  86. package/dist/{index-r066jx76.js → index-c6aagrbd.js} +148 -120
  87. package/dist/{index-k30h66yd.js → index-ck71tbcf.js} +9 -9
  88. package/dist/{index-eqqvgdb3.js → index-ct75c3at.js} +2 -2
  89. package/dist/{index-gw8bmyaj.js → index-djjhhgjk.js} +41 -41
  90. package/dist/{index-8qt2m66q.js → index-dvwj1f43.js} +43 -43
  91. package/dist/{index-fhwqwxe0.js → index-ej331hah.js} +1 -1
  92. package/dist/{index-9t3dffv2.js → index-ewbaywk5.js} +5 -5
  93. package/dist/{index-7p1f0vp2.js → index-fyphpknh.js} +25 -25
  94. package/dist/{index-83bdg97s.js → index-g149e9pk.js} +1 -1
  95. package/dist/{index-ngfhvfpd.js → index-g6a3na27.js} +4 -4
  96. package/dist/{index-qwhynv6x.js → index-h124epcf.js} +20 -20
  97. package/dist/{index-h6eeys1v.js → index-h97tqqp1.js} +9 -9
  98. package/dist/{index-zcgf3gqf.js → index-hby4qhax.js} +10 -10
  99. package/dist/{index-5jrd0yng.js → index-j8aw2c0e.js} +4 -4
  100. package/dist/{index-d8m8zpd8.js → index-jzczzcsb.js} +14 -14
  101. package/dist/index-kqnd6a85.js +26 -0
  102. package/dist/{index-35mh5bhj.js → index-kw1ws0cs.js} +12 -12
  103. package/dist/{index-yw4fzmdz.js → index-kzz6rq0w.js} +28 -17
  104. package/dist/{index-4gcnakhx.js → index-m38ej7e5.js} +2 -2
  105. package/dist/{index-ndddxtyx.js → index-m79yr938.js} +19 -19
  106. package/dist/{index-5p2m7we1.js → index-m9fndta6.js} +11 -11
  107. package/dist/index-mnkk1ffz.js +150 -0
  108. package/dist/index-n15jny5b.js +4 -0
  109. package/dist/{index-2vtcmrj9.js → index-n53mswt3.js} +20 -20
  110. package/dist/{index-bt14acb5.js → index-n5yqxd3d.js} +21 -21
  111. package/dist/{index-bm5rx7n6.js → index-n9tyz8z3.js} +12 -12
  112. package/dist/index-nmaqxdx7.js +463 -0
  113. package/dist/{index-mqv3b5rh.js → index-nx6eazt0.js} +30 -30
  114. package/dist/{index-3z73fh2c.js → index-q0th8q0z.js} +9 -9
  115. package/dist/{index-v260df89.js → index-qjeggp8w.js} +20 -20
  116. package/dist/{index-zfrhfn82.js → index-r1z0rd9c.js} +20 -20
  117. package/dist/{index-wqgjfstn.js → index-r5c4fedv.js} +3 -3
  118. package/dist/{index-cyktc4bp.js → index-r5hsgkz5.js} +118 -118
  119. package/dist/{index-0b1ess5e.js → index-r7a7r7ha.js} +1 -1
  120. package/dist/{index-1j8pm5t0.js → index-rfkh44ss.js} +15 -15
  121. package/dist/index-rz3z13e9.js +47 -0
  122. package/dist/{index-k6gpggb9.js → index-s80awwsw.js} +35 -35
  123. package/dist/{index-vqr7f7v8.js → index-ssmd41kh.js} +124 -124
  124. package/dist/{index-ensd0r74.js → index-v14qqmys.js} +41 -41
  125. package/dist/{index-paz6sgdp.js → index-v4a2fjtk.js} +36 -36
  126. package/dist/{index-d9pk3td4.js → index-w0eqdhyd.js} +14 -14
  127. package/dist/{index-5qn1kx34.js → index-w575evhq.js} +29 -29
  128. package/dist/{index-3gye9wzb.js → index-w5s3xftz.js} +26 -26
  129. package/dist/{index-tm88avnj.js → index-wykebpn9.js} +3 -3
  130. package/dist/{index-sh3s86na.js → index-xkhk5d6g.js} +17 -17
  131. package/dist/{index-6k1937bx.js → index-xqhede6x.js} +4 -4
  132. package/dist/{index-f4fx2tza.js → index-zs4ms3th.js} +181 -181
  133. package/dist/{index-k5hsfc3j.js → index-zsvzhjtk.js} +1 -1
  134. package/dist/observability/bounded-logger.d.ts +2 -0
  135. package/dist/observability/bounded-logger.d.ts.map +1 -1
  136. package/dist/observability/sanitize.d.ts +6 -0
  137. package/dist/observability/sanitize.d.ts.map +1 -1
  138. package/dist/observability/secret-values.d.ts +25 -0
  139. package/dist/observability/secret-values.d.ts.map +1 -0
  140. package/dist/react/keyboard/hooks.d.ts +71 -0
  141. package/dist/react/keyboard/hooks.d.ts.map +1 -0
  142. package/dist/react/keyboard/layers.d.ts +98 -0
  143. package/dist/react/keyboard/layers.d.ts.map +1 -0
  144. package/dist/react/keyboard/zone.d.ts +139 -0
  145. package/dist/react/keyboard/zone.d.ts.map +1 -0
  146. package/dist/telegram/crypto.d.ts +16 -0
  147. package/dist/telegram/crypto.d.ts.map +1 -0
  148. package/dist/telegram/html/check.d.ts +26 -0
  149. package/dist/telegram/html/check.d.ts.map +1 -0
  150. package/dist/telegram/html/nodes.d.ts +70 -0
  151. package/dist/telegram/html/nodes.d.ts.map +1 -0
  152. package/dist/telegram/html/parse.d.ts +35 -0
  153. package/dist/telegram/html/parse.d.ts.map +1 -0
  154. package/dist/telegram/html/render.d.ts +39 -0
  155. package/dist/telegram/html/render.d.ts.map +1 -0
  156. package/dist/telegram/init-data.d.ts.map +1 -1
  157. package/dist/telegram/operator-channel.d.ts +36 -3
  158. package/dist/telegram/operator-channel.d.ts.map +1 -1
  159. package/dist/telegram/screens/callback-codec.d.ts +34 -0
  160. package/dist/telegram/screens/callback-codec.d.ts.map +1 -0
  161. package/dist/telegram/screens/dispatch.d.ts +22 -0
  162. package/dist/telegram/screens/dispatch.d.ts.map +1 -0
  163. package/dist/telegram/screens/display.d.ts +70 -0
  164. package/dist/telegram/screens/display.d.ts.map +1 -0
  165. package/dist/telegram/screens/execute.d.ts +57 -0
  166. package/dist/telegram/screens/execute.d.ts.map +1 -0
  167. package/dist/telegram/screens/html.d.ts +36 -0
  168. package/dist/telegram/screens/html.d.ts.map +1 -0
  169. package/dist/telegram/screens/outcome.d.ts +74 -0
  170. package/dist/telegram/screens/outcome.d.ts.map +1 -0
  171. package/dist/telegram/screens/path.d.ts +29 -0
  172. package/dist/telegram/screens/path.d.ts.map +1 -0
  173. package/dist/telegram/screens/reconcile.d.ts +67 -0
  174. package/dist/telegram/screens/reconcile.d.ts.map +1 -0
  175. package/dist/telegram/screens/registry.d.ts +23 -0
  176. package/dist/telegram/screens/registry.d.ts.map +1 -0
  177. package/dist/telegram/screens/render.d.ts +70 -0
  178. package/dist/telegram/screens/render.d.ts.map +1 -0
  179. package/dist/telegram/screens/runtime.d.ts +133 -0
  180. package/dist/telegram/screens/runtime.d.ts.map +1 -0
  181. package/dist/telegram/screens/screen-types.d.ts +130 -0
  182. package/dist/telegram/screens/screen-types.d.ts.map +1 -0
  183. package/dist/telegram/screens/screen.d.ts +138 -0
  184. package/dist/telegram/screens/screen.d.ts.map +1 -0
  185. package/dist/telegram/screens/state.d.ts +115 -0
  186. package/dist/telegram/screens/state.d.ts.map +1 -0
  187. package/dist/telegram/screens/telegram-screens.d.ts +18 -0
  188. package/dist/telegram/screens/telegram-screens.d.ts.map +1 -0
  189. package/dist/telegram/screens/test-chat-telegram.d.ts +47 -0
  190. package/dist/telegram/screens/test-chat-telegram.d.ts.map +1 -0
  191. package/dist/telegram/screens/test-chat.d.ts +66 -0
  192. package/dist/telegram/screens/test-chat.d.ts.map +1 -0
  193. package/dist/telegram/screens/view.d.ts +89 -0
  194. package/dist/telegram/screens/view.d.ts.map +1 -0
  195. package/dist/telegram/send-failure.d.ts +19 -0
  196. package/dist/telegram/send-failure.d.ts.map +1 -1
  197. package/dist/telegram/update-intake.d.ts +78 -0
  198. package/dist/telegram/update-intake.d.ts.map +1 -0
  199. package/dist/telegram/update-store-sqlite.d.ts +26 -0
  200. package/dist/telegram/update-store-sqlite.d.ts.map +1 -0
  201. package/dist/telegram/update-store.d.ts +67 -0
  202. package/dist/telegram/update-store.d.ts.map +1 -0
  203. package/dist/telegram/webhook.d.ts +105 -0
  204. package/dist/telegram/webhook.d.ts.map +1 -0
  205. package/dist/tools/cli/args-fields.d.ts +8 -0
  206. package/dist/tools/cli/args-fields.d.ts.map +1 -1
  207. package/dist/tools/cli/args-route.d.ts.map +1 -1
  208. package/dist/tools/cli/args.d.ts.map +1 -1
  209. package/dist/tools/cli/help.d.ts.map +1 -1
  210. package/llms/application.2.txt +26 -1
  211. package/llms/application.txt +18 -27
  212. package/llms/cli.txt +8 -2
  213. package/llms/observability.txt +2 -1
  214. package/llms/react-keyboard.txt +193 -0
  215. package/llms/stitchkit.txt +7 -1
  216. package/llms/telegram-html.txt +35 -0
  217. package/llms/telegram-screens.txt +365 -0
  218. package/llms/telegram.txt +160 -4
  219. package/llms-full.txt +755 -11
  220. package/llms.txt +11 -6
  221. package/package.json +17 -2
  222. package/dist/index-2k4yrqkc.js +0 -9
  223. package/dist/index-crykmqkr.js +0 -26
  224. package/dist/index-y64k3s0h.js +0 -150
  225. package/dist/index-y91ry9h2.js +0 -4
  226. /package/dist/{index-hffjpmq9.js → index-9jfdm5tg.js} +0 -0
  227. /package/dist/{index-zjjyynm3.js → index-d4pa2bgs.js} +0 -0
  228. /package/dist/{index-rxfy4cq7.js → index-dwfv9a85.js} +0 -0
  229. /package/dist/{index-5jm36zv1.js → index-gfk7nrht.js} +0 -0
  230. /package/dist/{index-b9aefrqd.js → index-n1dqr7gf.js} +0 -0
  231. /package/dist/{index-q3pyb9n0.js → index-sykb5ag7.js} +0 -0
  232. /package/dist/{index-48ffdxgk.js → index-xhzq2qvj.js} +0 -0
  233. /package/dist/{index-kzfs85xp.js → index-zsarb5xp.js} +0 -0
@@ -1,6 +1,14 @@
1
1
  import {
2
- isRecord
3
- } from "../index-hffjpmq9.js";
2
+ classifyTelegramSendFailure2
3
+ } from "../index-6k1svmjh.js";
4
+ import {
5
+ TELEGRAM_TEXT_LIMIT2,
6
+ truncateTelegramHtml2
7
+ } from "../index-nmaqxdx7.js";
8
+ import {
9
+ isRecord,
10
+ transportResult
11
+ } from "../index-9jfdm5tg.js";
4
12
 
5
13
  // src/telegram/bot-api.ts
6
14
  var TELEGRAM_BOT_TOKEN_PATTERN = /\d{5,}:[A-Za-z0-9_-]{30,}/;
@@ -175,122 +183,6 @@ async function lockBroadcast(directory, files) {
175
183
  throw new Error("Telegram broadcast lock could not be taken");
176
184
  }
177
185
 
178
- // src/telegram/send-failure.ts
179
- var BY_DESCRIPTION = [
180
- [/bot was blocked by the user|bot was kicked/i, "blocked-by-user"],
181
- [/user is deactivated|account is deactivated/i, "user-deactivated"],
182
- [/can'?t initiate conversation|need to start a conversation/i, "not-started"],
183
- [/chat not found|peer_id_invalid|user not found/i, "chat-not-found"],
184
- [/too many requests|retry later|flood/i, "rate-limited"],
185
- [
186
- /message is too long|can'?t parse entities|message text is empty|wrong file identifier|button_url_invalid/i,
187
- "message-invalid"
188
- ]
189
- ];
190
- var RETRYABLE = new Set([
191
- "rate-limited",
192
- "server-error"
193
- ]);
194
- var UNREACHABLE = new Set([
195
- "blocked-by-user",
196
- "user-deactivated",
197
- "chat-not-found",
198
- "not-started"
199
- ]);
200
- function numberAt(value, keys) {
201
- if (!isRecord(value))
202
- return;
203
- for (const key of keys) {
204
- const candidate = value[key];
205
- if (typeof candidate === "number" && Number.isFinite(candidate))
206
- return candidate;
207
- }
208
- return;
209
- }
210
- function statusOf(value, depth = 0) {
211
- if (!isRecord(value) || depth > 4)
212
- return;
213
- const direct = numberAt(value, ["error_code", "statusCode", "status"]);
214
- if (direct !== undefined && direct >= 400 && direct < 600)
215
- return direct;
216
- const nested = value.cause ?? value.response ?? value.error;
217
- return nested === value ? undefined : statusOf(nested, depth + 1);
218
- }
219
- function retryAfterOf(value, depth = 0) {
220
- if (!isRecord(value) || depth > 4)
221
- return;
222
- const direct = numberAt(value.parameters, ["retry_after"]);
223
- if (direct !== undefined)
224
- return direct;
225
- const nested = value.cause ?? value.response ?? value.error;
226
- return nested === value ? undefined : retryAfterOf(nested, depth + 1);
227
- }
228
- function descriptionOf(value, depth = 0) {
229
- if (typeof value === "string")
230
- return value;
231
- if (value instanceof Error) {
232
- const nested2 = isRecord(value) ? descriptionOf(value.cause, depth + 1) : "";
233
- return `${value.message} ${nested2}`.trim();
234
- }
235
- if (!isRecord(value) || depth > 4)
236
- return "";
237
- const own = typeof value.description === "string" ? value.description : "";
238
- const message = typeof value.message === "string" ? value.message : "";
239
- const nested = descriptionOf(value.cause ?? value.response ?? value.error, depth + 1);
240
- return [own, message, nested].filter(Boolean).join(" ");
241
- }
242
- function classifyTelegramSendFailure(error) {
243
- const status = statusOf(error);
244
- const retryAfterSeconds = retryAfterOf(error);
245
- const description = descriptionOf(error);
246
- if (retryAfterSeconds !== undefined) {
247
- return {
248
- reason: "rate-limited",
249
- ...status !== undefined && { status },
250
- retryAfterSeconds,
251
- retryable: true,
252
- recipientUnreachable: false,
253
- evidence: "parameters"
254
- };
255
- }
256
- for (const [pattern, reason] of BY_DESCRIPTION) {
257
- if (pattern.test(description)) {
258
- return {
259
- reason,
260
- ...status !== undefined && { status },
261
- retryable: RETRYABLE.has(reason),
262
- recipientUnreachable: UNREACHABLE.has(reason),
263
- evidence: "description"
264
- };
265
- }
266
- }
267
- if (status === 429) {
268
- return {
269
- reason: "rate-limited",
270
- status,
271
- retryable: true,
272
- recipientUnreachable: false,
273
- evidence: "status"
274
- };
275
- }
276
- if (status !== undefined && status >= 500) {
277
- return {
278
- reason: "server-error",
279
- status,
280
- retryable: true,
281
- recipientUnreachable: false,
282
- evidence: "status"
283
- };
284
- }
285
- return {
286
- reason: "unknown",
287
- ...status !== undefined && { status },
288
- retryable: false,
289
- recipientUnreachable: false,
290
- evidence: "none"
291
- };
292
- }
293
-
294
186
  // src/telegram/broadcast.ts
295
187
  function pause(milliseconds) {
296
188
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -376,7 +268,7 @@ async function runTelegramBroadcast(config) {
376
268
  await config.send({ recipient, attempt });
377
269
  return { outcome: "delivered" };
378
270
  } catch (error) {
379
- const failure = classifyTelegramSendFailure(error);
271
+ const failure = classifyTelegramSendFailure2(error);
380
272
  if (failure.recipientUnreachable)
381
273
  return { outcome: "unreachable", reason: failure.reason };
382
274
  if (failure.reason === "message-invalid")
@@ -446,6 +338,28 @@ function telegramBroadcastSender(config) {
446
338
  }
447
339
  // src/telegram/init-data.ts
448
340
  import { z as z2 } from "zod";
341
+
342
+ // src/telegram/crypto.ts
343
+ var encoder = new TextEncoder;
344
+ async function hmacSha256(key, message) {
345
+ const imported = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
346
+ return crypto.subtle.sign("HMAC", imported, encoder.encode(message));
347
+ }
348
+ function toHex(buffer) {
349
+ return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
350
+ }
351
+ function digestsEqual(a, b) {
352
+ if (a.length !== b.length)
353
+ return false;
354
+ let difference = 0;
355
+ for (let index = 0;index < a.length; index += 1) {
356
+ difference |= a.charCodeAt(index) ^ b.charCodeAt(index);
357
+ }
358
+ return difference === 0;
359
+ }
360
+
361
+ // src/telegram/init-data.ts
362
+ var encoder2 = new TextEncoder;
449
363
  var TelegramInitDataUserSchema = z2.object({
450
364
  id: z2.int(),
451
365
  is_bot: z2.boolean().optional(),
@@ -467,23 +381,6 @@ var TelegramInitDataUserSchema = z2.object({
467
381
  ...user.allows_write_to_pm !== undefined && { allowsWriteToPm: user.allows_write_to_pm },
468
382
  ...user.photo_url !== undefined && { photoUrl: user.photo_url }
469
383
  }));
470
- var encoder = new TextEncoder;
471
- async function hmacSha256(key, message) {
472
- const imported = await crypto.subtle.importKey("raw", key, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
473
- return crypto.subtle.sign("HMAC", imported, encoder.encode(message));
474
- }
475
- function toHex(buffer) {
476
- return Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
477
- }
478
- function digestsEqual(a, b) {
479
- if (a.length !== b.length)
480
- return false;
481
- let difference = 0;
482
- for (let index = 0;index < a.length; index += 1) {
483
- difference |= a.charCodeAt(index) ^ b.charCodeAt(index);
484
- }
485
- return difference === 0;
486
- }
487
384
  function optionalUser(raw, key) {
488
385
  const encoded = raw[key];
489
386
  if (encoded === undefined)
@@ -508,7 +405,7 @@ async function verifyTelegramInitData(options) {
508
405
  }
509
406
  const dataCheckString = Object.keys(signed).sort().map((key) => `${key}=${signed[key]}`).join(`
510
407
  `);
511
- const secretKey = await hmacSha256(encoder.encode("WebAppData"), options.botToken);
408
+ const secretKey = await hmacSha256(encoder2.encode("WebAppData"), options.botToken);
512
409
  const expected = toHex(await hmacSha256(secretKey, dataCheckString));
513
410
  if (!digestsEqual(expected, hash.toLowerCase())) {
514
411
  return { valid: false, reason: "signature-mismatch" };
@@ -635,7 +532,6 @@ function createTelegramLocalFiles(config) {
635
532
  };
636
533
  }
637
534
  // src/telegram/operator-channel.ts
638
- var TEXT_LIMIT = 4096;
639
535
  var MASK = "[redacted]";
640
536
  function masked(text, patterns) {
641
537
  let output = text;
@@ -648,12 +544,12 @@ function literal(value) {
648
544
  return new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
649
545
  }
650
546
  function timer(milliseconds, signal) {
651
- return new Promise((resolve2, reject) => {
547
+ return new Promise((resolve, reject) => {
652
548
  if (signal.aborted)
653
549
  return reject(signal.reason);
654
550
  const handle = setTimeout(() => {
655
551
  signal.removeEventListener("abort", stop);
656
- resolve2();
552
+ resolve();
657
553
  }, milliseconds);
658
554
  const stop = () => {
659
555
  clearTimeout(handle);
@@ -664,14 +560,67 @@ function timer(milliseconds, signal) {
664
560
  }
665
561
  function fitted(text) {
666
562
  const points = Array.from(text);
667
- return points.length <= TEXT_LIMIT ? text : `${points.slice(0, TEXT_LIMIT - 1).join("")}…`;
563
+ return points.length <= TELEGRAM_TEXT_LIMIT2 ? text : `${points.slice(0, TELEGRAM_TEXT_LIMIT2 - 1).join("")}…`;
564
+ }
565
+ function defaultFingerprint(text, topic) {
566
+ const blanked = text.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "#").replace(/\b[a-z0-9]{20,}\b/gi, "#").replace(/\d+/g, "#");
567
+ return `${topic ?? ""}\x00${blanked.slice(0, 300)}`;
568
+ }
569
+ function dedupeWindow(options) {
570
+ const windowMs = options.windowMs ?? 600000;
571
+ const maxPerWindow = options.maxPerWindow ?? 20;
572
+ const fingerprint = options.fingerprint ?? defaultFingerprint;
573
+ const now = options.now ?? Date.now;
574
+ const repeatedLine = options.repeatedLine ?? ((count) => `(+${count} more like this)`);
575
+ const overBudgetLine = options.overBudgetLine ?? ((count) => `(+${count} more messages over the limit)`);
576
+ const recent = new Map;
577
+ let windowStart = Number.NEGATIVE_INFINITY;
578
+ let sentInWindow = 0;
579
+ let overBudget = 0;
580
+ return (text, topic) => {
581
+ const at = now();
582
+ const key = fingerprint(text, topic);
583
+ const previous = recent.get(key);
584
+ if (previous && at - previous.sentAt < windowMs) {
585
+ previous.held += 1;
586
+ return { send: false, reason: "repeated" };
587
+ }
588
+ if (at - windowStart >= windowMs) {
589
+ windowStart = at;
590
+ sentInWindow = 0;
591
+ }
592
+ if (sentInWindow >= maxPerWindow) {
593
+ overBudget += 1;
594
+ return { send: false, reason: "over-budget" };
595
+ }
596
+ sentInWindow += 1;
597
+ for (const [stale, entry] of recent) {
598
+ if (at - entry.sentAt >= windowMs)
599
+ recent.delete(stale);
600
+ }
601
+ recent.set(key, { sentAt: at, held: 0 });
602
+ const lines = [text];
603
+ if (previous?.held)
604
+ lines.push(repeatedLine(previous.held));
605
+ if (overBudget > 0)
606
+ lines.push(overBudgetLine(overBudget));
607
+ overBudget = 0;
608
+ return { send: true, text: lines.join(`
609
+
610
+ `) };
611
+ };
668
612
  }
669
613
  function createTelegramOperatorChannel(config) {
670
614
  const maxQueued = config.maxQueued ?? 200;
671
615
  const minIntervalMs = config.minIntervalMs ?? 3000;
672
616
  const maxAttempts = config.maxAttempts ?? 3;
673
617
  const sleep = config.sleep ?? timer;
674
- const patterns = [TELEGRAM_BOT_TOKEN_PATTERN, ...config.sensitivePatterns ?? []];
618
+ const patterns = [
619
+ TELEGRAM_BOT_TOKEN_PATTERN,
620
+ ...config.sensitivePatterns ?? [],
621
+ ...[...config.sensitiveValues ?? []].filter((value) => value !== "").sort((left, right) => right.length - left.length).map(literal)
622
+ ];
623
+ const admit = config.dedupe ? dedupeWindow(config.dedupe) : undefined;
675
624
  const queue = [];
676
625
  const idle = new Set;
677
626
  let lifetime = new AbortController;
@@ -708,7 +657,7 @@ function createTelegramOperatorChannel(config) {
708
657
  } catch (error) {
709
658
  if (signal.aborted)
710
659
  throw error;
711
- const failure = classifyTelegramSendFailure(error);
660
+ const failure = classifyTelegramSendFailure2(error);
712
661
  if (!failure.retryable) {
713
662
  drop(item, "refused", failure);
714
663
  return;
@@ -759,6 +708,13 @@ function createTelegramOperatorChannel(config) {
759
708
  drop(item, "closed");
760
709
  return;
761
710
  }
711
+ const verdict = admit?.(item.text, topic);
712
+ if (verdict && !verdict.send) {
713
+ drop(item, verdict.reason);
714
+ return;
715
+ }
716
+ if (verdict)
717
+ item.text = verdict.text;
762
718
  queue.push(item);
763
719
  while (queue.length > maxQueued) {
764
720
  const oldest = queue.shift();
@@ -777,11 +733,11 @@ function createTelegramOperatorChannel(config) {
777
733
  drain(signal) {
778
734
  if (queue.length === 0 && inFlight === 0)
779
735
  return Promise.resolve();
780
- return new Promise((resolve2) => {
736
+ return new Promise((resolve) => {
781
737
  const done = () => {
782
738
  signal?.removeEventListener("abort", done);
783
739
  idle.delete(done);
784
- resolve2();
740
+ resolve();
785
741
  };
786
742
  idle.add(done);
787
743
  signal?.addEventListener("abort", done, { once: true });
@@ -797,6 +753,9 @@ function createTelegramOperatorChannel(config) {
797
753
  }
798
754
  };
799
755
  }
756
+ function markupFitted(text, parseMode) {
757
+ return parseMode === "HTML" ? truncateTelegramHtml2(text) : text;
758
+ }
800
759
  function telegramOperatorSender(config) {
801
760
  const token = literal(config.token);
802
761
  return (message, signal) => callTelegramBotApi({
@@ -804,7 +763,7 @@ function telegramOperatorSender(config) {
804
763
  method: "sendMessage",
805
764
  params: {
806
765
  chat_id: message.chatId,
807
- text: masked(message.text, [token]),
766
+ text: markupFitted(masked(message.text, [token]), config.parseMode),
808
767
  ...message.threadId !== undefined && { message_thread_id: message.threadId },
809
768
  ...config.parseMode && { parse_mode: config.parseMode },
810
769
  link_preview_options: { is_disabled: true }
@@ -814,16 +773,449 @@ function telegramOperatorSender(config) {
814
773
  ...config.fetch && { fetch: config.fetch }
815
774
  });
816
775
  }
776
+ // src/telegram/update-intake.ts
777
+ var SWEEP_BATCH = 100;
778
+ function defaultRetry(error, attempt) {
779
+ const failure = classifyTelegramSendFailure2(error);
780
+ if (failure.evidence !== "none" && !failure.retryable)
781
+ return false;
782
+ const backoff = Math.min(5000 * 4 ** (attempt - 1), 15 * 60000);
783
+ return Math.max(backoff, (failure.retryAfterSeconds ?? 0) * 1000);
784
+ }
785
+ function lineOf(update) {
786
+ for (const [key, payload] of Object.entries(update)) {
787
+ if (key === "update_id" || !isRecord(payload))
788
+ continue;
789
+ const message = isRecord(payload.message) ? payload.message : undefined;
790
+ for (const holder of [
791
+ payload.chat,
792
+ message?.chat,
793
+ payload.from,
794
+ payload.user,
795
+ payload.voter_chat
796
+ ]) {
797
+ if (isRecord(holder) && (typeof holder.id === "number" || typeof holder.id === "string")) {
798
+ return `chat:${holder.id}`;
799
+ }
800
+ }
801
+ }
802
+ return `update:${String(update.update_id)}`;
803
+ }
804
+ function parsed(body) {
805
+ try {
806
+ const value = JSON.parse(body);
807
+ return isRecord(value) && Number.isSafeInteger(value.update_id) && Number(value.update_id) >= 0 ? value : undefined;
808
+ } catch {
809
+ return;
810
+ }
811
+ }
812
+ function positive(name, value) {
813
+ if (!Number.isSafeInteger(value) || value < 1) {
814
+ throw new RangeError(`[stitchkit] telegram update intake: ${name} is a positive integer`);
815
+ }
816
+ return value;
817
+ }
818
+ function createTelegramUpdateIntake(config) {
819
+ const { store } = config;
820
+ const now = config.now ?? Date.now;
821
+ const retry = config.retry ?? defaultRetry;
822
+ const maxAttempts = positive("maxAttempts", config.maxAttempts ?? 5);
823
+ const leaseMs = positive("leaseMs", config.leaseMs ?? 120000);
824
+ const pendingGraceMs = config.pendingGraceMs ?? 60000;
825
+ const sweepEveryMs = positive("sweepEveryMs", config.sweepEveryMs ?? 30000);
826
+ const retainMs = config.retainMs ?? 86400000;
827
+ const maxConcurrent = positive("maxConcurrent", config.maxConcurrent ?? 32);
828
+ const lines = new Map;
829
+ const scheduled = new Set;
830
+ const waiting = [];
831
+ let running = 0;
832
+ let started = false;
833
+ let timer;
834
+ const storeError = (error, step) => {
835
+ try {
836
+ config.onStoreError?.(error, step);
837
+ } catch {}
838
+ };
839
+ const slot = async () => {
840
+ if (running < maxConcurrent) {
841
+ running += 1;
842
+ return;
843
+ }
844
+ await new Promise((resolve) => waiting.push(resolve));
845
+ };
846
+ const release = () => {
847
+ const next = waiting.shift();
848
+ if (next)
849
+ next();
850
+ else
851
+ running -= 1;
852
+ };
853
+ const attempt = async (update, updateId) => {
854
+ const taken = now();
855
+ const number = await store.claim(updateId, { now: taken, leaseUntil: taken + leaseMs, maxAttempts }).catch((error) => storeError(error, "claim"));
856
+ if (number === undefined)
857
+ return;
858
+ const renewal = setInterval(() => {
859
+ store.renew(updateId, number, now() + leaseMs).catch((error) => storeError(error, "renew"));
860
+ }, Math.max(1, Math.floor(leaseMs / 4)));
861
+ let settlement;
862
+ try {
863
+ await config.handle(transportResult(update));
864
+ settlement = { state: "completed", at: now() };
865
+ } catch (error) {
866
+ const delay = number >= maxAttempts ? false : retry(error, number);
867
+ const message = error instanceof Error ? error.message : String(error);
868
+ settlement = delay === false ? { state: "abandoned", at: now(), error: message } : { state: "failed", at: now(), retryAt: now() + delay, error: message };
869
+ try {
870
+ config.onFailure?.({ updateId, attempt: number, error, retrying: delay !== false });
871
+ } catch {}
872
+ } finally {
873
+ clearInterval(renewal);
874
+ }
875
+ await store.settle(updateId, number, settlement).catch((error) => storeError(error, "settle"));
876
+ };
877
+ const schedule = (update) => {
878
+ const updateId = Number(update.update_id);
879
+ if (!started || scheduled.has(updateId))
880
+ return;
881
+ scheduled.add(updateId);
882
+ const line = lineOf(update);
883
+ const next = (lines.get(line) ?? Promise.resolve()).then(async () => {
884
+ await slot();
885
+ try {
886
+ await attempt(update, updateId);
887
+ } finally {
888
+ release();
889
+ }
890
+ }).finally(() => {
891
+ scheduled.delete(updateId);
892
+ if (lines.get(line) === next)
893
+ lines.delete(line);
894
+ });
895
+ lines.set(line, next);
896
+ };
897
+ const sweep = async (pendingBefore) => {
898
+ const at = now();
899
+ const due = await store.due({ now: at, pendingBefore, limit: SWEEP_BATCH });
900
+ let count = 0;
901
+ for (const row of due) {
902
+ const update = parsed(row.body);
903
+ if (update && !scheduled.has(row.updateId)) {
904
+ schedule(update);
905
+ count += 1;
906
+ }
907
+ }
908
+ await store.prune(at - retainMs);
909
+ return count;
910
+ };
911
+ return {
912
+ async accept(body) {
913
+ const update = parsed(body);
914
+ if (!update)
915
+ return "invalid";
916
+ const updateId = Number(update.update_id);
917
+ const added = await store.add({ updateId, body, receivedAt: now() });
918
+ if (!added)
919
+ return "duplicate";
920
+ schedule(update);
921
+ return "accepted";
922
+ },
923
+ async start() {
924
+ if (started)
925
+ return;
926
+ started = true;
927
+ await sweep(now());
928
+ timer = setInterval(() => {
929
+ sweep(now() - pendingGraceMs).catch((error) => storeError(error, "sweep"));
930
+ }, sweepEveryMs);
931
+ timer.unref?.();
932
+ },
933
+ sweep: () => sweep(now() - pendingGraceMs),
934
+ async idle() {
935
+ while (lines.size > 0)
936
+ await Promise.all(lines.values());
937
+ },
938
+ async close() {
939
+ started = false;
940
+ if (timer)
941
+ clearInterval(timer);
942
+ timer = undefined;
943
+ while (lines.size > 0)
944
+ await Promise.all(lines.values());
945
+ }
946
+ };
947
+ }
948
+ // src/telegram/update-store.ts
949
+ function isClaimable(record, now) {
950
+ if (record.state === "pending")
951
+ return true;
952
+ if (record.state === "failed")
953
+ return record.dueAt <= now;
954
+ return record.state === "processing" && record.dueAt < now;
955
+ }
956
+ function memoryTelegramUpdateStore() {
957
+ const records = new Map;
958
+ const holding = (updateId, attempt) => {
959
+ const record = records.get(updateId);
960
+ return record?.state === "processing" && record.attempts === attempt ? record : undefined;
961
+ };
962
+ return {
963
+ async add(update) {
964
+ if (records.has(update.updateId))
965
+ return false;
966
+ records.set(update.updateId, {
967
+ ...update,
968
+ state: "pending",
969
+ attempts: 0,
970
+ dueAt: update.receivedAt,
971
+ settledAt: undefined,
972
+ error: undefined
973
+ });
974
+ return true;
975
+ },
976
+ async claim(updateId, options) {
977
+ const record = records.get(updateId);
978
+ if (!record || !isClaimable(record, options.now))
979
+ return;
980
+ if (record.attempts >= options.maxAttempts) {
981
+ Object.assign(record, {
982
+ state: "abandoned",
983
+ settledAt: options.now,
984
+ error: "attempts spent"
985
+ });
986
+ return;
987
+ }
988
+ record.state = "processing";
989
+ record.attempts += 1;
990
+ record.dueAt = options.leaseUntil;
991
+ return record.attempts;
992
+ },
993
+ async renew(updateId, attempt, leaseUntil) {
994
+ const record = holding(updateId, attempt);
995
+ if (record)
996
+ record.dueAt = leaseUntil;
997
+ return record !== undefined;
998
+ },
999
+ async settle(updateId, attempt, settlement) {
1000
+ const record = holding(updateId, attempt);
1001
+ if (!record)
1002
+ return;
1003
+ record.state = settlement.state;
1004
+ record.error = settlement.state === "completed" ? undefined : settlement.error;
1005
+ if (settlement.state === "failed")
1006
+ record.dueAt = settlement.retryAt;
1007
+ else
1008
+ record.settledAt = settlement.at;
1009
+ },
1010
+ async due(query) {
1011
+ return [...records.values()].filter((record) => record.state === "pending" ? record.receivedAt <= query.pendingBefore : isClaimable(record, query.now)).sort((left, right) => left.updateId - right.updateId).slice(0, query.limit).map(({ updateId, body }) => ({ updateId, body }));
1012
+ },
1013
+ async prune(before) {
1014
+ let pruned = 0;
1015
+ for (const [updateId, record] of records) {
1016
+ if (record.settledAt !== undefined && record.settledAt < before) {
1017
+ records.delete(updateId);
1018
+ pruned += 1;
1019
+ }
1020
+ }
1021
+ return pruned;
1022
+ }
1023
+ };
1024
+ }
1025
+ // src/telegram/update-store-sqlite.ts
1026
+ var TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,62}$/;
1027
+ var CLAIMABLE = `(state = 'pending' OR (state = 'failed' AND due_at <= ?1)
1028
+ OR (state = 'processing' AND due_at < ?1))`;
1029
+ function rowOf(row) {
1030
+ if (typeof row !== "object" || row === null)
1031
+ return;
1032
+ const updateId = "update_id" in row ? row.update_id : undefined;
1033
+ const body = "body" in row ? row.body : undefined;
1034
+ return typeof updateId === "number" && typeof body === "string" ? { updateId, body } : undefined;
1035
+ }
1036
+ function sqliteTelegramUpdateStore(config) {
1037
+ const table = config.table ?? "telegram_updates";
1038
+ if (!TABLE_NAME.test(table)) {
1039
+ throw new TypeError("[stitchkit] telegram update store: a table name is [A-Za-z_][A-Za-z0-9_]*");
1040
+ }
1041
+ const database = config.database;
1042
+ database.exec(`CREATE TABLE IF NOT EXISTS ${table} (
1043
+ update_id INTEGER PRIMARY KEY,
1044
+ body TEXT NOT NULL,
1045
+ state TEXT NOT NULL,
1046
+ attempts INTEGER NOT NULL,
1047
+ due_at INTEGER NOT NULL,
1048
+ received_at INTEGER NOT NULL,
1049
+ settled_at INTEGER,
1050
+ error TEXT
1051
+ );
1052
+ CREATE INDEX IF NOT EXISTS ${table}_due ON ${table} (state, due_at);`);
1053
+ const insert = database.prepare(`INSERT OR IGNORE INTO ${table} (update_id, body, state, attempts, due_at, received_at)
1054
+ VALUES (?1, ?2, 'pending', 0, ?3, ?3)`);
1055
+ const take = database.prepare(`UPDATE ${table} SET state = 'processing', attempts = attempts + 1, due_at = ?2, error = NULL
1056
+ WHERE update_id = ?3 AND attempts < ?4 AND ${CLAIMABLE} RETURNING attempts`);
1057
+ const spend = database.prepare(`UPDATE ${table} SET state = 'abandoned', settled_at = ?1, error = 'attempts spent'
1058
+ WHERE update_id = ?2 AND attempts >= ?3 AND ${CLAIMABLE}`);
1059
+ const renew = database.prepare(`UPDATE ${table} SET due_at = ?1 WHERE update_id = ?2 AND attempts = ?3 AND state = 'processing'`);
1060
+ const settle = database.prepare(`UPDATE ${table} SET state = ?1, due_at = COALESCE(?2, due_at), settled_at = ?3, error = ?4
1061
+ WHERE update_id = ?5 AND attempts = ?6 AND state = 'processing'`);
1062
+ const due = database.prepare(`SELECT update_id, body FROM ${table}
1063
+ WHERE (state = 'pending' AND received_at <= ?2) OR (state <> 'pending' AND ${CLAIMABLE})
1064
+ ORDER BY update_id LIMIT ?3`);
1065
+ const prune = database.prepare(`DELETE FROM ${table} WHERE state IN ('completed', 'abandoned') AND settled_at < ?1`);
1066
+ return {
1067
+ async add(update) {
1068
+ return Number(insert.run(update.updateId, update.body, update.receivedAt).changes) > 0;
1069
+ },
1070
+ async claim(updateId, options) {
1071
+ const [row] = take.all(options.now, options.leaseUntil, updateId, options.maxAttempts);
1072
+ const attempts = typeof row === "object" && row !== null && "attempts" in row ? row.attempts : undefined;
1073
+ if (typeof attempts === "number")
1074
+ return attempts;
1075
+ spend.run(options.now, updateId, options.maxAttempts);
1076
+ return;
1077
+ },
1078
+ async renew(updateId, attempt, leaseUntil) {
1079
+ return Number(renew.run(leaseUntil, updateId, attempt).changes) > 0;
1080
+ },
1081
+ async settle(updateId, attempt, settlement) {
1082
+ settle.run(settlement.state, settlement.state === "failed" ? settlement.retryAt : null, settlement.state === "failed" ? null : settlement.at, settlement.state === "completed" ? null : settlement.error, updateId, attempt);
1083
+ },
1084
+ async due(query) {
1085
+ return due.all(query.now, query.pendingBefore, query.limit).flatMap((row) => rowOf(row) ?? []);
1086
+ },
1087
+ async prune(before) {
1088
+ return Number(prune.run(before).changes);
1089
+ }
1090
+ };
1091
+ }
1092
+ // src/telegram/webhook.ts
1093
+ var OWNER_PARAMETER = "owner";
1094
+ var OWNER_CONTEXT = "stitchkit:telegram-webhook-owner";
1095
+ var SECRET_SHAPE = /^[A-Za-z0-9_-]{1,256}$/;
1096
+ var SECRET_HEADER = "x-telegram-bot-api-secret-token";
1097
+ var TELEGRAM_WEBHOOK_NONE = "none";
1098
+
1099
+ class TelegramWebhookClaimError extends Error {
1100
+ reason;
1101
+ ownerHost;
1102
+ constructor(reason, ownerHost) {
1103
+ super(reason === "not-confirmed" ? `Telegram webhook was set but points to ${ownerHost} after it` : reason === "other-secret" ? `Telegram webhook at ${ownerHost} is registered with another secret; refusing to ` + `replace it. takeoverFrom: '${ownerHost}' replaces it` : `Telegram webhook belongs to ${ownerHost}; refusing to take it over. ` + `takeoverFrom: '${ownerHost}' moves it here`);
1104
+ this.name = "TelegramWebhookClaimError";
1105
+ this.reason = reason;
1106
+ this.ownerHost = ownerHost;
1107
+ }
1108
+ }
1109
+ function hostOf(url) {
1110
+ if (url === "")
1111
+ return TELEGRAM_WEBHOOK_NONE;
1112
+ return URL.canParse(url) ? new URL(url).host : "unknown";
1113
+ }
1114
+ function withoutTag(url) {
1115
+ if (!URL.canParse(url))
1116
+ return url;
1117
+ const parsed = new URL(url);
1118
+ parsed.searchParams.delete(OWNER_PARAMETER);
1119
+ return parsed.toString();
1120
+ }
1121
+ async function telegramWebhookUrl(url, secret) {
1122
+ if (!SECRET_SHAPE.test(secret)) {
1123
+ throw new TypeError("[stitchkit] telegram webhook: a secret is 1–256 of A-Z, a-z, 0-9, _ and -");
1124
+ }
1125
+ const tagged = new URL(url);
1126
+ const tag = toHex(await hmacSha256(new TextEncoder().encode(secret), OWNER_CONTEXT));
1127
+ tagged.searchParams.set(OWNER_PARAMETER, tag.slice(0, 16));
1128
+ return tagged.toString();
1129
+ }
1130
+ async function webhookInfo(config) {
1131
+ const result = await callTelegramBotApi({ ...botCall(config), method: "getWebhookInfo" });
1132
+ const info = isRecord(result) ? result : {};
1133
+ const at = info.last_error_date;
1134
+ const message = info.last_error_message;
1135
+ return {
1136
+ url: typeof info.url === "string" ? info.url : "",
1137
+ pendingUpdates: typeof info.pending_update_count === "number" ? info.pending_update_count : 0,
1138
+ ...typeof at === "number" && typeof message === "string" && { lastError: { at: at * 1000, message } }
1139
+ };
1140
+ }
1141
+ function botCall(config) {
1142
+ return {
1143
+ token: config.token,
1144
+ ...config.apiRoot && { apiRoot: config.apiRoot },
1145
+ ...config.fetch && { fetch: config.fetch },
1146
+ ...config.signal && { signal: config.signal }
1147
+ };
1148
+ }
1149
+ async function claimTelegramWebhook(config) {
1150
+ const url = await telegramWebhookUrl(config.url, config.secret);
1151
+ const current = await webhookInfo(config);
1152
+ const ownerHost = hostOf(current.url);
1153
+ const owned = current.url === url;
1154
+ if (!owned && config.takeoverFrom !== ownerHost) {
1155
+ const sameAddress = current.url !== "" && withoutTag(current.url) === withoutTag(url);
1156
+ throw new TelegramWebhookClaimError(sameAddress ? "other-secret" : "owned-elsewhere", ownerHost);
1157
+ }
1158
+ await callTelegramBotApi({
1159
+ ...botCall(config),
1160
+ method: "setWebhook",
1161
+ params: {
1162
+ url,
1163
+ secret_token: config.secret,
1164
+ drop_pending_updates: config.dropPendingUpdates ?? false,
1165
+ ...config.allowedUpdates && { allowed_updates: config.allowedUpdates },
1166
+ ...config.maxConnections !== undefined && { max_connections: config.maxConnections }
1167
+ }
1168
+ });
1169
+ const confirmed = await webhookInfo(config);
1170
+ if (confirmed.url !== url) {
1171
+ throw new TelegramWebhookClaimError("not-confirmed", hostOf(confirmed.url));
1172
+ }
1173
+ return {
1174
+ url,
1175
+ ...!owned && { takenFrom: ownerHost },
1176
+ pendingUpdates: confirmed.pendingUpdates
1177
+ };
1178
+ }
1179
+ async function checkTelegramWebhook(config) {
1180
+ const url = await telegramWebhookUrl(config.url, config.secret);
1181
+ const info = await webhookInfo(config);
1182
+ return {
1183
+ owned: info.url === url,
1184
+ ownerHost: hostOf(info.url),
1185
+ pendingUpdates: info.pendingUpdates,
1186
+ ...info.lastError && { lastError: info.lastError }
1187
+ };
1188
+ }
1189
+ async function receiveTelegramWebhook(request, options) {
1190
+ if (request.method !== "POST") {
1191
+ return new Response(null, { status: 405, headers: { allow: "POST" } });
1192
+ }
1193
+ const secret = request.headers.get(SECRET_HEADER);
1194
+ if (secret === null || !digestsEqual(secret, options.secret)) {
1195
+ return new Response(null, { status: 403 });
1196
+ }
1197
+ const acceptance = await options.accept(await request.text());
1198
+ return new Response(null, { status: acceptance === "invalid" ? 400 : 200 });
1199
+ }
817
1200
  export {
818
- verifyTelegramInitData,
819
- telegramOperatorSender,
820
- telegramBroadcastSender,
821
- runTelegramBroadcast,
822
- createTelegramOperatorChannel,
823
- createTelegramLocalFiles,
824
- classifyTelegramSendFailure,
825
- callTelegramBotApi,
826
- TelegramLocalFileError,
1201
+ TELEGRAM_BOT_TOKEN_PATTERN,
1202
+ TELEGRAM_WEBHOOK_NONE,
827
1203
  TelegramBotApiError,
828
- TELEGRAM_BOT_TOKEN_PATTERN
1204
+ TelegramLocalFileError,
1205
+ TelegramWebhookClaimError,
1206
+ callTelegramBotApi,
1207
+ checkTelegramWebhook,
1208
+ claimTelegramWebhook,
1209
+ classifyTelegramSendFailure2 as classifyTelegramSendFailure,
1210
+ createTelegramLocalFiles,
1211
+ createTelegramOperatorChannel,
1212
+ createTelegramUpdateIntake,
1213
+ memoryTelegramUpdateStore,
1214
+ receiveTelegramWebhook,
1215
+ runTelegramBroadcast,
1216
+ sqliteTelegramUpdateStore,
1217
+ telegramBroadcastSender,
1218
+ telegramOperatorSender,
1219
+ telegramWebhookUrl,
1220
+ verifyTelegramInitData
829
1221
  };