stitchkit 0.116.0 → 0.117.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 (120) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/agent-runtime/run-acquisition.d.ts +9 -2
  3. package/dist/agent-runtime/run-acquisition.d.ts.map +1 -1
  4. package/dist/agent-runtime/run-execution.d.ts +3 -0
  5. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  6. package/dist/agent-runtime/run-failure.d.ts +4 -1
  7. package/dist/agent-runtime/run-failure.d.ts.map +1 -1
  8. package/dist/agent-runtime/run-store-retry.d.ts +8 -0
  9. package/dist/agent-runtime/run-store-retry.d.ts.map +1 -0
  10. package/dist/agent-runtime/run-terminal.d.ts.map +1 -1
  11. package/dist/agent-runtime/run-unacquired.d.ts +19 -0
  12. package/dist/agent-runtime/run-unacquired.d.ts.map +1 -0
  13. package/dist/agent-runtime/store-conformance.d.ts +8 -0
  14. package/dist/agent-runtime/store-conformance.d.ts.map +1 -1
  15. package/dist/agent-runtime/store-driver-contract.d.ts +5 -0
  16. package/dist/agent-runtime/store-driver-contract.d.ts.map +1 -1
  17. package/dist/agent-runtime/terminal-commit.d.ts +34 -4
  18. package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
  19. package/dist/entrypoints/agent-runtime/harness.js +2 -2
  20. package/dist/entrypoints/agent-runtime/sqlite/bun.js +3 -3
  21. package/dist/entrypoints/agent-runtime/sqlite/node.js +3 -3
  22. package/dist/entrypoints/agent-runtime/testing.js +22 -4
  23. package/dist/entrypoints/agent-runtime.d.ts +1 -1
  24. package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
  25. package/dist/entrypoints/agent-runtime.js +13 -11
  26. package/dist/entrypoints/application/diagnostic-journal.js +1 -1
  27. package/dist/entrypoints/application/directory-inbox.js +3 -3
  28. package/dist/entrypoints/cli/publish.js +2 -2
  29. package/dist/entrypoints/files.js +2 -2
  30. package/dist/entrypoints/primitives.js +51 -154
  31. package/dist/entrypoints/server.js +6 -6
  32. package/dist/entrypoints/telegram.d.ts +1 -1
  33. package/dist/entrypoints/telegram.d.ts.map +1 -1
  34. package/dist/entrypoints/telegram.js +196 -76
  35. package/dist/entrypoints/tracking/contract.d.ts +11 -0
  36. package/dist/entrypoints/tracking/contract.d.ts.map +1 -0
  37. package/dist/entrypoints/tracking/contract.js +162 -0
  38. package/dist/entrypoints/tracking/performance.d.ts +8 -0
  39. package/dist/entrypoints/tracking/performance.d.ts.map +1 -0
  40. package/dist/entrypoints/tracking/performance.js +181 -0
  41. package/dist/entrypoints/tracking/server.d.ts +1 -0
  42. package/dist/entrypoints/tracking/server.d.ts.map +1 -1
  43. package/dist/entrypoints/tracking/server.js +40 -12
  44. package/dist/entrypoints/tracking.d.ts +4 -3
  45. package/dist/entrypoints/tracking.d.ts.map +1 -1
  46. package/dist/entrypoints/tracking.js +43 -143
  47. package/dist/entrypoints/xlsx.d.ts +4 -0
  48. package/dist/entrypoints/xlsx.d.ts.map +1 -0
  49. package/dist/entrypoints/xlsx.js +406 -0
  50. package/dist/{index-fa435yq2.js → index-073nw9qr.js} +8 -8
  51. package/dist/index-1f40z584.js +7 -0
  52. package/dist/{index-fdk4r4r6.js → index-75ns2ybj.js} +1 -1
  53. package/dist/{index-jq2ky8fw.js → index-cycp3r3e.js} +124 -181
  54. package/dist/{index-4g6qmgdx.js → index-ghtpfz4z.js} +9 -9
  55. package/dist/{index-n3v695sb.js → index-hdjmdwsj.js} +277 -96
  56. package/dist/{index-zqkz4af9.js → index-nqxrm176.js} +1 -1
  57. package/dist/index-qezhmp51.js +242 -0
  58. package/dist/index-vv1sch8a.js +94 -0
  59. package/dist/{index-fggxgbrk.js → index-xx1j310k.js} +165 -102
  60. package/dist/internal/exclusive-lock.d.ts.map +1 -1
  61. package/dist/primitives/decimal.d.ts +10 -0
  62. package/dist/primitives/decimal.d.ts.map +1 -1
  63. package/dist/primitives/index.d.ts +3 -1
  64. package/dist/primitives/index.d.ts.map +1 -1
  65. package/dist/primitives/money.d.ts +20 -0
  66. package/dist/primitives/money.d.ts.map +1 -1
  67. package/dist/primitives/quantity.d.ts.map +1 -1
  68. package/dist/primitives/rounding.d.ts +40 -0
  69. package/dist/primitives/rounding.d.ts.map +1 -0
  70. package/dist/telegram/budget-transformer.d.ts +2 -2
  71. package/dist/telegram/not-dispatched.d.ts +2 -2
  72. package/dist/telegram/not-dispatched.d.ts.map +1 -1
  73. package/dist/telegram/outbound-budget.d.ts +53 -16
  74. package/dist/telegram/outbound-budget.d.ts.map +1 -1
  75. package/dist/telegram/outbound-cost.d.ts +5 -0
  76. package/dist/telegram/outbound-cost.d.ts.map +1 -0
  77. package/dist/telegram/outbound-pauses.d.ts +17 -0
  78. package/dist/telegram/outbound-pauses.d.ts.map +1 -0
  79. package/dist/telegram/outbound-queue.d.ts +17 -0
  80. package/dist/telegram/outbound-queue.d.ts.map +1 -0
  81. package/dist/tracking/attribution.d.ts +1 -1
  82. package/dist/tracking/attribution.d.ts.map +1 -1
  83. package/dist/tracking/client-visit.d.ts +3 -1
  84. package/dist/tracking/client-visit.d.ts.map +1 -1
  85. package/dist/tracking/client.d.ts.map +1 -1
  86. package/dist/tracking/performance/collector.d.ts +61 -0
  87. package/dist/tracking/performance/collector.d.ts.map +1 -0
  88. package/dist/tracking/performance/schema.d.ts +45 -0
  89. package/dist/tracking/performance/schema.d.ts.map +1 -0
  90. package/dist/tracking/performance/summary.d.ts +12 -0
  91. package/dist/tracking/performance/summary.d.ts.map +1 -0
  92. package/dist/tracking/privacy.d.ts +5 -0
  93. package/dist/tracking/privacy.d.ts.map +1 -0
  94. package/dist/tracking/server/telemetry-health.d.ts +31 -0
  95. package/dist/tracking/server/telemetry-health.d.ts.map +1 -0
  96. package/dist/xlsx/cells.d.ts +17 -0
  97. package/dist/xlsx/cells.d.ts.map +1 -0
  98. package/dist/xlsx/render.d.ts +11 -0
  99. package/dist/xlsx/render.d.ts.map +1 -0
  100. package/dist/xlsx/schema.d.ts +119 -0
  101. package/dist/xlsx/schema.d.ts.map +1 -0
  102. package/dist/xlsx/zip.d.ts +13 -0
  103. package/dist/xlsx/zip.d.ts.map +1 -0
  104. package/llms/agent-runtime.2.txt +40 -16
  105. package/llms/agent-runtime.3.txt +17 -0
  106. package/llms/agent-runtime.txt +4 -1
  107. package/llms/primitives.txt +21 -0
  108. package/llms/stitchkit.txt +9 -6
  109. package/llms/telegram.txt +32 -12
  110. package/llms/tracking-contract.txt +32 -0
  111. package/llms/tracking-performance.txt +21 -0
  112. package/llms/tracking-server.txt +1 -0
  113. package/llms/tracking.txt +75 -12
  114. package/llms/{upgrading-0.105.0-0.116.0.txt → upgrading-0.105.0-0.117.0.txt} +50 -3
  115. package/llms/upgrading.txt +1 -1
  116. package/llms/xlsx.txt +107 -0
  117. package/llms-full.txt +355 -31
  118. package/llms.txt +11 -7
  119. package/package.json +27 -9
  120. package/dist/{index-z3d6smer.js → index-tdpmfks8.js} +5 -5
@@ -5,7 +5,7 @@ import"../index-2kbysk9s.js";
5
5
  import"../index-n8z3rvzw.js";
6
6
  import {
7
7
  attemptExclusiveLock
8
- } from "../index-z3d6smer.js";
8
+ } from "../index-tdpmfks8.js";
9
9
  import {
10
10
  TELEGRAM_SEND_FAILURE_REASONS2,
11
11
  classifyTelegramSendFailure2
@@ -908,11 +908,8 @@ function telegramOperatorSender(config) {
908
908
  }
909
909
  // src/telegram/outbound-budget.ts
910
910
  import { z as z5 } from "zod";
911
- var DEFAULT_MESSAGES_PER_SECOND = 24;
912
- var DEFAULT_RETRY_AFTER_FALLBACK_MS = 1000;
913
- var TOKEN = z5.string().min(1, "token must not be empty");
914
- var MESSAGES_PER_SECOND = z5.number().positive().max(1000);
915
- var RETRY_AFTER_FALLBACK_MS = z5.number().int().nonnegative().max(MAX_TIMER_MS);
911
+
912
+ // src/telegram/outbound-cost.ts
916
913
  var SEND_METHODS = new Set([
917
914
  "sendMessage",
918
915
  "sendPhoto",
@@ -959,15 +956,69 @@ function sendCost(method, payload) {
959
956
  const value = payload[field];
960
957
  return Array.isArray(value) ? Math.max(1, value.length) : 1;
961
958
  }
962
- function createBudget(rate, fallback, now, sleepFor) {
963
- const interval = 1000 / rate;
959
+ function chatOf(payload) {
960
+ if (!isRecord(payload))
961
+ return;
962
+ const chat = payload.chat_id;
963
+ return typeof chat === "number" || typeof chat === "string" ? String(chat) : undefined;
964
+ }
965
+
966
+ // src/telegram/outbound-pauses.ts
967
+ function createOutboundPauses({ tokenPauseChats, tokenPauseWindowMs }, now, onPause) {
968
+ const chatPauses = new Map;
969
+ const refusedChats = new Map;
970
+ let pausedUntil = Number.NEGATIVE_INFINITY;
971
+ const pauseEnd = (milliseconds) => {
972
+ if (Number.isNaN(milliseconds) || milliseconds < 0) {
973
+ throw new RangeError("pause must be a non-negative number of milliseconds");
974
+ }
975
+ return now() + (Number.isFinite(milliseconds) ? milliseconds : Number.MAX_SAFE_INTEGER);
976
+ };
977
+ const pauseToken = (milliseconds) => {
978
+ pausedUntil = Math.max(pausedUntil, pauseEnd(milliseconds));
979
+ onPause();
980
+ };
981
+ const forgetExpired = (current) => {
982
+ for (const [chat, until] of chatPauses)
983
+ if (until <= current)
984
+ chatPauses.delete(chat);
985
+ for (const [chat, at] of refusedChats) {
986
+ if (current - at > tokenPauseWindowMs)
987
+ refusedChats.delete(chat);
988
+ }
989
+ };
990
+ return {
991
+ resumesAt: (chat) => Math.max(pausedUntil, (chat === undefined ? undefined : chatPauses.get(chat)) ?? Number.NEGATIVE_INFINITY),
992
+ pauseToken,
993
+ pauseChat(chat, milliseconds) {
994
+ const until = pauseEnd(milliseconds);
995
+ const current = now();
996
+ forgetExpired(current);
997
+ chatPauses.set(chat, Math.max(chatPauses.get(chat) ?? until, until));
998
+ refusedChats.set(chat, current);
999
+ onPause();
1000
+ if (refusedChats.size < tokenPauseChats)
1001
+ return;
1002
+ refusedChats.clear();
1003
+ pauseToken(milliseconds);
1004
+ },
1005
+ pausedForMs: () => Math.max(0, pausedUntil - now()),
1006
+ pausedChats() {
1007
+ forgetExpired(now());
1008
+ return chatPauses.size;
1009
+ }
1010
+ };
1011
+ }
1012
+
1013
+ // src/telegram/outbound-queue.ts
1014
+ function createOutboundQueue(interval, now, sleepFor, pauses) {
964
1015
  const queue = [];
965
1016
  let nextGrantAt = now();
966
- let pausedUntil = Number.NEGATIVE_INFINITY;
967
1017
  let granted = 0;
968
1018
  let messages = 0;
969
1019
  let draining = false;
970
- let wake;
1020
+ let waking;
1021
+ let wakingAt = Number.POSITIVE_INFINITY;
971
1022
  const wait = async (milliseconds, signal) => {
972
1023
  const delay = Math.min(MAX_TIMER_MS, Math.max(0, milliseconds));
973
1024
  if (delay <= 0)
@@ -978,20 +1029,40 @@ function createBudget(rate, fallback, now, sleepFor) {
978
1029
  else
979
1030
  await pending;
980
1031
  };
981
- const wakeWaiting = () => {
982
- const current = wake;
983
- wake = undefined;
1032
+ const wake = () => {
1033
+ const current = waking;
1034
+ waking = undefined;
984
1035
  current?.abort();
985
1036
  };
986
- const nextWaiter = () => queue.find((waiter) => waiter.lane === "interactive") ?? queue[0];
1037
+ const nextWaiter = (current) => {
1038
+ let bulk;
1039
+ let held;
1040
+ for (const waiter of queue) {
1041
+ if (pauses.resumesAt(waiter.chat) > current) {
1042
+ if (held === undefined || pauses.resumesAt(waiter.chat) < pauses.resumesAt(held.chat)) {
1043
+ held = waiter;
1044
+ }
1045
+ } else if (waiter.lane === "interactive") {
1046
+ return waiter;
1047
+ } else {
1048
+ bulk ??= waiter;
1049
+ }
1050
+ }
1051
+ return bulk ?? held;
1052
+ };
1053
+ const detach = (waiter) => {
1054
+ if (waiter.onAbort)
1055
+ waiter.signal?.removeEventListener("abort", waiter.onAbort);
1056
+ waiter.expiry?.abort();
1057
+ };
987
1058
  const remove = (waiter) => {
988
1059
  const index = queue.indexOf(waiter);
989
1060
  if (index === -1)
990
1061
  return;
991
- const next = nextWaiter() === waiter;
1062
+ const next = nextWaiter(now()) === waiter;
992
1063
  queue.splice(index, 1);
993
1064
  if (next)
994
- wakeWaiting();
1065
+ wake();
995
1066
  };
996
1067
  const drain = async () => {
997
1068
  if (draining)
@@ -999,38 +1070,38 @@ function createBudget(rate, fallback, now, sleepFor) {
999
1070
  draining = true;
1000
1071
  try {
1001
1072
  while (queue.length > 0) {
1002
- const head = nextWaiter();
1073
+ const current = now();
1074
+ const head = nextWaiter(current);
1003
1075
  if (head === undefined)
1004
1076
  break;
1005
1077
  if (head.signal?.aborted) {
1006
1078
  remove(head);
1007
1079
  continue;
1008
1080
  }
1009
- const current = now();
1010
- const at = Math.max(current, nextGrantAt, pausedUntil);
1081
+ const at = Math.max(current, nextGrantAt, pauses.resumesAt(head.chat));
1011
1082
  if (at > current) {
1012
1083
  const controller = new AbortController;
1013
- wake = controller;
1084
+ waking = controller;
1085
+ wakingAt = at;
1014
1086
  try {
1015
1087
  await wait(at - current, controller.signal);
1016
1088
  } catch (error) {
1017
1089
  if (!controller.signal.aborted) {
1018
1090
  for (const waiter of queue.splice(0)) {
1019
- if (waiter.onAbort)
1020
- waiter.signal?.removeEventListener("abort", waiter.onAbort);
1091
+ detach(waiter);
1021
1092
  waiter.reject(error);
1022
1093
  }
1023
1094
  return;
1024
1095
  }
1025
1096
  } finally {
1026
- if (wake === controller)
1027
- wake = undefined;
1097
+ if (waking === controller)
1098
+ waking = undefined;
1099
+ wakingAt = Number.POSITIVE_INFINITY;
1028
1100
  }
1029
1101
  continue;
1030
1102
  }
1031
1103
  queue.splice(queue.indexOf(head), 1);
1032
- if (head.onAbort)
1033
- head.signal?.removeEventListener("abort", head.onAbort);
1104
+ detach(head);
1034
1105
  nextGrantAt = at + head.cost * interval;
1035
1106
  granted += 1;
1036
1107
  messages += head.cost;
@@ -1042,67 +1113,115 @@ function createBudget(rate, fallback, now, sleepFor) {
1042
1113
  drain();
1043
1114
  }
1044
1115
  };
1045
- const acquire = (cost, signal, options) => {
1046
- if (!Number.isSafeInteger(cost) || cost < 1) {
1047
- return Promise.reject(new RangeError("cost must be an integer of at least one message"));
1048
- }
1049
- if (signal?.aborted)
1050
- return Promise.reject(signal.reason);
1051
- return new Promise((resolve, reject) => {
1052
- const waiter = {
1053
- cost,
1054
- lane: options?.lane ?? "interactive",
1055
- resolve,
1056
- reject,
1057
- ...signal && { signal }
1058
- };
1059
- if (signal) {
1060
- const onAbort = () => {
1061
- remove(waiter);
1062
- reject(signal.reason);
1063
- };
1064
- waiter.onAbort = onAbort;
1065
- signal.addEventListener("abort", onAbort, { once: true });
1066
- }
1067
- queue.push(waiter);
1068
- drain();
1116
+ const expireAfter = (waiter, maxWaitMs, reject) => {
1117
+ waiter.expiry = new AbortController;
1118
+ wait(maxWaitMs, waiter.expiry.signal).then(() => {
1119
+ if (!queue.includes(waiter))
1120
+ return;
1121
+ remove(waiter);
1122
+ detach(waiter);
1123
+ reject(new TelegramNotDispatchedError2("queue", "the Telegram request waited longer than maxWaitMs for its turn"));
1124
+ }, () => {
1125
+ return;
1069
1126
  });
1070
1127
  };
1071
- const pauseFor = (milliseconds) => {
1072
- if (Number.isNaN(milliseconds) || milliseconds < 0) {
1073
- throw new RangeError("pause must be a non-negative number of milliseconds");
1074
- }
1075
- const duration = Number.isFinite(milliseconds) ? milliseconds : Number.MAX_SAFE_INTEGER;
1076
- pausedUntil = Math.max(pausedUntil, now() + duration);
1077
- wakeWaiting();
1128
+ return {
1129
+ acquire(cost, signal, options) {
1130
+ if (!Number.isSafeInteger(cost) || cost < 1) {
1131
+ return Promise.reject(new RangeError("cost must be an integer of at least one message"));
1132
+ }
1133
+ if (signal?.aborted)
1134
+ return Promise.reject(signal.reason);
1135
+ const maxWaitMs = options?.maxWaitMs;
1136
+ if (maxWaitMs !== undefined && (!Number.isSafeInteger(maxWaitMs) || maxWaitMs < 1 || maxWaitMs > MAX_TIMER_MS)) {
1137
+ return Promise.reject(new RangeError(`maxWaitMs must be an integer from 1 to ${MAX_TIMER_MS}`));
1138
+ }
1139
+ const chat = options?.chatId === undefined ? undefined : String(options.chatId);
1140
+ return new Promise((resolve, reject) => {
1141
+ const waiter = {
1142
+ cost,
1143
+ lane: options?.lane ?? "interactive",
1144
+ chat,
1145
+ resolve,
1146
+ reject,
1147
+ ...signal && { signal }
1148
+ };
1149
+ if (signal) {
1150
+ const onAbort = () => {
1151
+ remove(waiter);
1152
+ detach(waiter);
1153
+ reject(signal.reason);
1154
+ };
1155
+ waiter.onAbort = onAbort;
1156
+ signal.addEventListener("abort", onAbort, { once: true });
1157
+ }
1158
+ if (maxWaitMs !== undefined)
1159
+ expireAfter(waiter, maxWaitMs, reject);
1160
+ queue.push(waiter);
1161
+ if (Math.max(now(), nextGrantAt, pauses.resumesAt(chat)) < wakingAt)
1162
+ wake();
1163
+ drain();
1164
+ });
1165
+ },
1166
+ wake,
1167
+ wait,
1168
+ counts: () => ({
1169
+ granted,
1170
+ messages,
1171
+ queued: queue.length,
1172
+ queuedBulk: queue.filter((waiter) => waiter.lane === "bulk").length
1173
+ })
1078
1174
  };
1079
- const pauseAfter = (error) => {
1175
+ }
1176
+
1177
+ // src/telegram/outbound-budget.ts
1178
+ var DEFAULT_MESSAGES_PER_SECOND = 24;
1179
+ var DEFAULT_RETRY_AFTER_FALLBACK_MS = 1000;
1180
+ var DEFAULT_TOKEN_PAUSE_CHATS = 3;
1181
+ var DEFAULT_TOKEN_PAUSE_WINDOW_MS = 5000;
1182
+ var TOKEN = z5.string().min(1, "token must not be empty");
1183
+ var MESSAGES_PER_SECOND = z5.number().positive().max(1000);
1184
+ var RETRY_AFTER_FALLBACK_MS = z5.number().int().nonnegative().max(MAX_TIMER_MS);
1185
+ var TOKEN_PAUSE_CHATS = z5.number().int().min(2).max(1000);
1186
+ var TOKEN_PAUSE_WINDOW_MS = z5.number().int().positive().max(MAX_TIMER_MS);
1187
+ function createBudget(policy, now, sleepFor) {
1188
+ const { rate, fallback } = policy;
1189
+ const pauses = createOutboundPauses(policy, now, () => queue.wake());
1190
+ const queue = createOutboundQueue(1000 / rate, now, sleepFor, pauses);
1191
+ const pauseAfter = (error, options) => {
1080
1192
  const failure = classifyTelegramSendFailure2(error);
1081
1193
  if (failure.reason !== "rate-limited")
1082
1194
  return;
1083
- pauseFor(failure.retryAfterSeconds === undefined ? fallback : failure.retryAfterSeconds * 1000);
1195
+ const milliseconds = failure.retryAfterSeconds === undefined ? fallback : failure.retryAfterSeconds * 1000;
1196
+ if (options?.chatId === undefined)
1197
+ pauses.pauseToken(milliseconds);
1198
+ else
1199
+ pauses.pauseChat(String(options.chatId), milliseconds);
1084
1200
  };
1085
- const waitUntilResumed = async (signal) => {
1201
+ const waitUntilResumed = async (signal, options) => {
1202
+ const chat = options?.chatId === undefined ? undefined : String(options.chatId);
1086
1203
  while (true) {
1087
1204
  if (signal?.aborted)
1088
1205
  throw signal.reason;
1089
- const remaining = pausedUntil - now();
1206
+ const remaining = pauses.resumesAt(chat) - now();
1090
1207
  if (remaining <= 0)
1091
1208
  return;
1092
- await wait(Math.min(remaining, MAX_TIMER_MS), signal);
1209
+ await queue.wait(Math.min(remaining, MAX_TIMER_MS), signal);
1093
1210
  }
1094
1211
  };
1095
1212
  const run = async (method, payload, signal, send, options) => {
1096
1213
  if (signal?.aborted)
1097
1214
  throw signal.reason;
1098
1215
  const cost = sendCost(method, payload);
1216
+ const chatId = options?.chatId ?? chatOf(payload);
1217
+ const call = chatId === undefined ? {} : { chatId };
1099
1218
  if (cost > 0)
1100
- await acquire(cost, signal, options);
1219
+ await queue.acquire(cost, signal, { ...options, ...call });
1101
1220
  if (signal?.aborted)
1102
1221
  throw signal.reason;
1103
1222
  const pauseOnRefusal = (refusal) => {
1104
- if (cost > 0)
1105
- pauseAfter(refusal);
1223
+ if (cost > 0 && options?.refusal !== "call")
1224
+ pauseAfter(refusal, call);
1106
1225
  };
1107
1226
  let result;
1108
1227
  try {
@@ -1115,22 +1234,19 @@ function createBudget(rate, fallback, now, sleepFor) {
1115
1234
  pauseOnRefusal(result);
1116
1235
  return result;
1117
1236
  };
1118
- const budget = {
1237
+ return {
1119
1238
  run,
1120
- acquire,
1121
- pause: pauseFor,
1239
+ acquire: queue.acquire,
1240
+ pause: pauses.pauseToken,
1122
1241
  pauseAfter,
1123
1242
  waitUntilResumed,
1124
1243
  stats: () => ({
1125
- granted,
1126
- messages,
1127
- queued: queue.length,
1128
- queuedBulk: queue.filter((waiter) => waiter.lane === "bulk").length,
1244
+ ...queue.counts(),
1129
1245
  messagesPerSecond: rate,
1130
- pausedForMs: Math.max(0, pausedUntil - now())
1246
+ pausedForMs: pauses.pausedForMs(),
1247
+ pausedChats: pauses.pausedChats()
1131
1248
  })
1132
1249
  };
1133
- return budget;
1134
1250
  }
1135
1251
  var budgets = new Map;
1136
1252
  function keyOf(token) {
@@ -1140,20 +1256,24 @@ function telegramOutboundBudget(config) {
1140
1256
  const token = TOKEN.parse(config.token);
1141
1257
  const rate = MESSAGES_PER_SECOND.parse(config.messagesPerSecond ?? DEFAULT_MESSAGES_PER_SECOND);
1142
1258
  const fallback = RETRY_AFTER_FALLBACK_MS.parse(config.retryAfterFallbackMs ?? DEFAULT_RETRY_AFTER_FALLBACK_MS);
1259
+ const tokenPauseChats = TOKEN_PAUSE_CHATS.parse(config.tokenPauseChats ?? DEFAULT_TOKEN_PAUSE_CHATS);
1260
+ const tokenPauseWindowMs = TOKEN_PAUSE_WINDOW_MS.parse(config.tokenPauseWindowMs ?? DEFAULT_TOKEN_PAUSE_WINDOW_MS);
1143
1261
  const key = keyOf(token);
1144
1262
  const existing = budgets.get(key);
1145
1263
  if (existing) {
1146
- const same = existing.rate === rate && existing.fallback === fallback && existing.nowOption === config.now && existing.sleepOption === config.sleep;
1264
+ const same = existing.rate === rate && existing.fallback === fallback && existing.tokenPauseChats === tokenPauseChats && existing.tokenPauseWindowMs === tokenPauseWindowMs && existing.nowOption === config.now && existing.sleepOption === config.sleep;
1147
1265
  if (!same) {
1148
1266
  throw new Error("a Telegram outbound budget already exists for this token with different options");
1149
1267
  }
1150
1268
  return existing.budget;
1151
1269
  }
1152
- const budget = createBudget(rate, fallback, config.now ?? (() => performance.now()), config.sleep ?? sleep);
1270
+ const budget = createBudget({ rate, fallback, tokenPauseChats, tokenPauseWindowMs }, config.now ?? (() => performance.now()), config.sleep ?? sleep);
1153
1271
  budgets.set(key, {
1154
1272
  budget,
1155
1273
  rate,
1156
1274
  fallback,
1275
+ tokenPauseChats,
1276
+ tokenPauseWindowMs,
1157
1277
  nowOption: config.now,
1158
1278
  sleepOption: config.sleep
1159
1279
  });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `stitchkit/tracking/contract` — the wire shapes of tracking: the contract
3
+ * factory, the event and visit schemas, and the performance sample with its
4
+ * trust threshold. It brings `zod`, so it belongs to the server and to a shared
5
+ * contract, and browser code that sends events does not import it. → ADR 0280.
6
+ */
7
+ export { createTrackingContract, type TrackingContractConfig, type TrackingContractEndpoints, } from '../../tracking/contract.js';
8
+ export { type PerformanceSample, PerformanceSampleSchema, type PerformanceVitals, PerformanceVitalsSchema, } from '../../tracking/performance/schema.js';
9
+ export { type PerformanceMetricSummary, summarizePerformanceMetric, } from '../../tracking/performance/summary.js';
10
+ export { type AttributionData, AttributionDataSchema, createTrackingSchemas, DEFAULT_BUILD_ID_PATTERN, type TrackEventsRequest, type TrackEventsResponse, TrackEventsResponseSchema, type TrackingDisposition, TrackingDispositionSchema, type TrackingDispositionStatus, TrackingDispositionStatusSchema, type TrackingEventEnvelope, type TrackingEventShape, type TrackingOutboxState, TrackingOutboxStateSchema, type TrackingSchemas, type TrackingSchemasConfig, type UtmData, UtmDataSchema, type VisitBootstrapResponse, VisitBootstrapResponseSchema, type VisitEntryContext, type VisitEntryContextShape, } from '../../tracking/schemas.js';
11
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/entrypoints/tracking/contract.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,sBAAsB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,GAC/B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,KAAK,iBAAiB,EACtB,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,uBAAuB,GACxB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,KAAK,wBAAwB,EAC7B,0BAA0B,GAC3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,KAAK,eAAe,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,+BAA+B,EAC/B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,OAAO,EACZ,aAAa,EACb,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,162 @@
1
+ import"../../index-m38ej7e5.js";
2
+ import {
3
+ defineContract2
4
+ } from "../../index-d9hq291n.js";
5
+
6
+ // src/tracking/schemas.ts
7
+ import { z } from "zod";
8
+ var UtmDataSchema = z.object({
9
+ source: z.string().max(200).optional(),
10
+ medium: z.string().max(200).optional(),
11
+ campaign: z.string().max(200).optional(),
12
+ content: z.string().max(200).optional(),
13
+ term: z.string().max(200).optional()
14
+ });
15
+ var AttributionDataSchema = z.object({
16
+ utm: UtmDataSchema.optional(),
17
+ referrer: z.string().optional(),
18
+ landingPage: z.string().optional()
19
+ });
20
+ var DEFAULT_BUILD_ID_PATTERN = /^(dev|[a-f0-9]{7,40})$/;
21
+ var TrackingDispositionStatusSchema = z.enum([
22
+ "accepted",
23
+ "duplicate",
24
+ "identity-invalid",
25
+ "excluded-bot"
26
+ ]);
27
+ var TrackingDispositionSchema = z.object({
28
+ eventId: z.uuid(),
29
+ status: TrackingDispositionStatusSchema
30
+ });
31
+ var TrackEventsResponseSchema = z.object({
32
+ accepted: z.number().int().nonnegative(),
33
+ dispositions: z.array(TrackingDispositionSchema)
34
+ });
35
+ var VisitBootstrapResponseSchema = z.object({
36
+ visitId: z.uuid(),
37
+ expiresAt: z.iso.datetime()
38
+ });
39
+ var TrackingOutboxStateSchema = z.enum(["available", "unavailable"]);
40
+ function trackingEventShape(eventTypes) {
41
+ return {
42
+ eventId: z.uuid(),
43
+ visitId: z.uuid(),
44
+ browserStreamId: z.uuid(),
45
+ browserSequence: z.number().int().nonnegative(),
46
+ type: z.enum(eventTypes),
47
+ page: z.string().max(2000),
48
+ metadata: z.record(z.string(), z.unknown()).optional(),
49
+ clientTimestamp: z.number()
50
+ };
51
+ }
52
+ function visitEntryContextShape(buildIdPattern) {
53
+ return {
54
+ browserStreamId: z.uuid(),
55
+ previousVisitId: z.uuid().optional(),
56
+ origin: z.url().max(500),
57
+ landingPath: z.string().startsWith("/").max(300).regex(/^[^?#]*$/),
58
+ referrer: z.url().max(2000).optional(),
59
+ utm: UtmDataSchema.optional(),
60
+ displayMode: z.enum(["browser", "standalone"]),
61
+ screenWidth: z.number().int().positive().max(20000),
62
+ screenHeight: z.number().int().positive().max(20000),
63
+ buildId: z.string().regex(buildIdPattern),
64
+ outboxState: TrackingOutboxStateSchema,
65
+ outboxQueued: z.number().int().nonnegative().max(1000),
66
+ outboxDropped: z.number().int().nonnegative()
67
+ };
68
+ }
69
+ function createTrackingSchemas(config) {
70
+ const base = z.object(trackingEventShape(config.eventTypes));
71
+ const event = config.eventExtras ? base.extend(config.eventExtras.shape) : base;
72
+ const buildIdPattern = config.buildIdPattern ?? DEFAULT_BUILD_ID_PATTERN;
73
+ const maxEvents = config.maxEventsPerBatch ?? 50;
74
+ return {
75
+ event,
76
+ request: z.object({
77
+ buildId: z.string().regex(buildIdPattern),
78
+ events: z.array(event).min(1).max(maxEvents),
79
+ utm: UtmDataSchema.optional()
80
+ }),
81
+ response: TrackEventsResponseSchema,
82
+ disposition: TrackingDispositionSchema,
83
+ entry: z.object(visitEntryContextShape(buildIdPattern)),
84
+ bootstrap: VisitBootstrapResponseSchema
85
+ };
86
+ }
87
+
88
+ // src/tracking/contract.ts
89
+ function createTrackingContract(config) {
90
+ const schemas = createTrackingSchemas(config);
91
+ return defineContract2({ prefix: config.prefix ?? "tracking", scope: config.scope }, {
92
+ bootstrap: {
93
+ method: "POST",
94
+ path: "/visit",
95
+ expose: ["HTTP"],
96
+ desc: "Issue or renew a browser visit lease",
97
+ input: schemas.entry,
98
+ output: schemas.bootstrap
99
+ },
100
+ track: {
101
+ method: "POST",
102
+ path: "/events",
103
+ expose: ["HTTP"],
104
+ desc: "Batch track browser events",
105
+ input: schemas.request,
106
+ output: schemas.response,
107
+ safelistedBody: true,
108
+ maxJsonBodyBytes: config.maxJsonBodyBytes ?? 256 * 1024
109
+ }
110
+ });
111
+ }
112
+ // src/tracking/performance/schema.ts
113
+ import { z as z2 } from "zod";
114
+ var BoundedMsSchema = z2.number().nonnegative().max(3600000).nullable();
115
+ var PerformanceVitalsSchema = z2.object({
116
+ lcpMs: BoundedMsSchema,
117
+ inpMs: BoundedMsSchema,
118
+ fcpMs: BoundedMsSchema,
119
+ ttfbMs: BoundedMsSchema,
120
+ cls: z2.number().nonnegative().max(100).nullable()
121
+ });
122
+ var PerformanceSampleSchema = z2.object({
123
+ schemaVersion: z2.literal(1),
124
+ id: z2.string().min(1).max(64),
125
+ kind: z2.enum(["early", "final"]),
126
+ route: z2.string().min(1).max(2000),
127
+ buildId: z2.string().min(1).max(64),
128
+ visitId: z2.string().min(1).max(128).nullable(),
129
+ deviceClass: z2.enum(["mobile", "desktop"]),
130
+ navigationType: z2.string().min(1).max(32).nullable(),
131
+ vitals: PerformanceVitalsSchema,
132
+ capturedAt: z2.number().int().nonnegative()
133
+ });
134
+ // src/tracking/performance/summary.ts
135
+ var DEFAULT_MIN_SAMPLES = 100;
136
+ function percentile(ascending, rank) {
137
+ return ascending[Math.ceil(ascending.length * rank) - 1] ?? null;
138
+ }
139
+ function summarizePerformanceMetric(values, options = {}) {
140
+ const present = values.filter((value) => value !== null).sort((left, right) => left - right);
141
+ const trusted = present.length >= (options.minSamples ?? DEFAULT_MIN_SAMPLES);
142
+ return {
143
+ samples: present.length,
144
+ p75: percentile(present, 0.75),
145
+ p95: trusted ? percentile(present, 0.95) : null
146
+ };
147
+ }
148
+ export {
149
+ AttributionDataSchema,
150
+ DEFAULT_BUILD_ID_PATTERN,
151
+ PerformanceSampleSchema,
152
+ PerformanceVitalsSchema,
153
+ TrackEventsResponseSchema,
154
+ TrackingDispositionSchema,
155
+ TrackingDispositionStatusSchema,
156
+ TrackingOutboxStateSchema,
157
+ UtmDataSchema,
158
+ VisitBootstrapResponseSchema,
159
+ createTrackingContract,
160
+ createTrackingSchemas,
161
+ summarizePerformanceMetric
162
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `stitchkit/tracking/performance` — Web Vitals from real visitors, collected
3
+ * after the page has loaded. The sample's schema and the report's trust threshold
4
+ * are `stitchkit/tracking/contract`; tables and reports stay the application's.
5
+ * Importing this needs the optional peer `web-vitals`. → ADR 0280.
6
+ */
7
+ export { createPerformanceCollector, isLabUserAgent, type PerformanceCollector, type PerformanceCollectorConfig, type PerformanceHost, type VitalsLibrary, type VitalsMetric, type VitalsObserver, } from '../../tracking/performance/collector.js';
8
+ //# sourceMappingURL=performance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performance.d.ts","sourceRoot":"","sources":["../../../src/entrypoints/tracking/performance.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,sCAAsC,CAAC"}