stitchkit 0.96.0 → 0.96.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.
package/CHANGELOG.md CHANGED
@@ -15,6 +15,18 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.96.1] — 2026-09-24
19
+
20
+ ### Fixed
21
+
22
+ - `stitchkit/telegram` — **the operator channel no longer posts a bot token.**
23
+ Bots post their errors to the operators' chat, and an error about a local
24
+ Bot API file carries `<root>/<token>/…` in its path; the journal masked it,
25
+ the chat did not. `createTelegramOperatorChannel` now masks a bot token in
26
+ every message before it is queued — so a dropped message is reported masked
27
+ too — plus any `sensitivePatterns` given, and `telegramOperatorSender` masks
28
+ its own exact token whatever its shape.
29
+
18
30
  ## [0.96.0] — 2026-09-24
19
31
 
20
32
  ### ⚠️ Breaking changes
@@ -636,6 +636,17 @@ function createTelegramLocalFiles(config) {
636
636
  }
637
637
  // src/telegram/operator-channel.ts
638
638
  var TEXT_LIMIT = 4096;
639
+ var MASK = "[redacted]";
640
+ function masked(text, patterns) {
641
+ let output = text;
642
+ for (const pattern of patterns) {
643
+ output = output.replace(new RegExp(pattern.source, `${pattern.flags.replace(/[gy]/g, "")}g`), MASK);
644
+ }
645
+ return output;
646
+ }
647
+ function literal(value) {
648
+ return new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
649
+ }
639
650
  function timer(milliseconds, signal) {
640
651
  return new Promise((resolve2, reject) => {
641
652
  if (signal.aborted)
@@ -660,6 +671,7 @@ function createTelegramOperatorChannel(config) {
660
671
  const minIntervalMs = config.minIntervalMs ?? 3000;
661
672
  const maxAttempts = config.maxAttempts ?? 3;
662
673
  const sleep = config.sleep ?? timer;
674
+ const patterns = [TELEGRAM_BOT_TOKEN_PATTERN, ...config.sensitivePatterns ?? []];
663
675
  const queue = [];
664
676
  const idle = new Set;
665
677
  let lifetime = new AbortController;
@@ -740,7 +752,7 @@ function createTelegramOperatorChannel(config) {
740
752
  try {
741
753
  const item = {
742
754
  ...topic !== undefined && { topic },
743
- text,
755
+ text: masked(text, patterns),
744
756
  attempts: 0
745
757
  };
746
758
  if (closed) {
@@ -786,12 +798,13 @@ function createTelegramOperatorChannel(config) {
786
798
  };
787
799
  }
788
800
  function telegramOperatorSender(config) {
801
+ const token = literal(config.token);
789
802
  return (message, signal) => callTelegramBotApi({
790
803
  token: config.token,
791
804
  method: "sendMessage",
792
805
  params: {
793
806
  chat_id: message.chatId,
794
- text: message.text,
807
+ text: masked(message.text, [token]),
795
808
  ...message.threadId !== undefined && { message_thread_id: message.threadId },
796
809
  ...config.parseMode && { parse_mode: config.parseMode },
797
810
  link_preview_options: { is_disabled: true }
@@ -48,6 +48,12 @@ export interface TelegramOperatorChannelConfig<TTopic extends string> {
48
48
  readonly onDropped?: (drop: TelegramOperatorDrop<TTopic>) => void;
49
49
  /** Default: a timer. Rejects when `signal` aborts. */
50
50
  readonly sleep?: (milliseconds: number, signal: AbortSignal) => Promise<void>;
51
+ /**
52
+ * More secrets to mask in every message before it leaves. A bot token is
53
+ * always masked: bots post their errors here, and an error about a local Bot
54
+ * API file carries `<root>/<token>/…` in its path.
55
+ */
56
+ readonly sensitivePatterns?: readonly RegExp[];
51
57
  }
52
58
  export interface TelegramOperatorChannel<TTopic extends string> {
53
59
  /** Queue one message. Returns at once and never throws. */
@@ -1 +1 @@
1
- {"version":3,"file":"operator-channel.d.ts","sourceRoot":"","sources":["../../src/telegram/operator-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAA+B,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAEvF,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAK7C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB,CAAC,MAAM,SAAS,MAAM;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,CAAC;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CACxC;AAED,MAAM,WAAW,6BAA6B,CAAC,MAAM,SAAS,MAAM;IAClE,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAChC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5D,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,uBAAuB,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3F,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gGAAgG;IAChG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,oDAAoD;IACpD,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,oBAAoB,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IAClE,sDAAsD;IACtD,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,uBAAuB,CAAC,MAAM,SAAS,MAAM;IAC5D,2DAA2D;IAC3D,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,KAAK,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,kFAAkF;IAClF,KAAK,IAAI,IAAI,CAAC;CACf;AA4BD,wBAAgB,6BAA6B,CAAC,MAAM,SAAS,MAAM,GAAG,KAAK,EACzE,MAAM,EAAE,6BAA6B,CAAC,MAAM,CAAC,GAC5C,uBAAuB,CAAC,MAAM,CAAC,CA8IjC;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,iFAAiF;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,4BAA4B,GACnC,6BAA6B,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAgB/C"}
1
+ {"version":3,"file":"operator-channel.d.ts","sourceRoot":"","sources":["../../src/telegram/operator-channel.ts"],"names":[],"mappings":"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"}
package/llms/telegram.txt CHANGED
@@ -77,7 +77,10 @@ const operators = createTelegramOperatorChannel<'users' | 'payments' | 'errors'>
77
77
  operators.post(`New user ${user.id}`, 'users')
78
78
  ```
79
79
 
80
- What to post stays the bot's. On the way down, drain it within the grace period
80
+ Every message is masked before it leaves: a bot token always — bots post their
81
+ errors here, and an error about a local Bot API file carries the token in its
82
+ path — plus any `sensitivePatterns` the bot adds; `telegramOperatorSender` also
83
+ masks its own exact token. What to post stays the bot's. On the way down, drain it within the grace period
81
84
  and close it: `drain: (context) => operators.drain(context.signal)`,
82
85
  `close: () => operators.close()` in a resource.
83
86
 
@@ -176,7 +179,7 @@ injected grammY bot. → ADR 0143, ADR 0201 — [guide](../guide/telegram.md)
176
179
  | `TelegramBroadcastConfig` / `TelegramBroadcastSend` / `TelegramBroadcastReport` / `TelegramBroadcastRunOutcome` / `TelegramBroadcastOutcome` / `TelegramBroadcastRecipient` | _type_ | `name`, `directory`, `recipients()`, `send({ recipient, attempt })`, `maxAttempts`, `signal`, `onProgress`; counts `delivered` / `unreachable` / `failed` / `uncertain` / `pending` and `finished` / `stopped` / `halted` / `dry-run` |
177
180
  | `telegramBroadcastSender` / `TelegramBroadcastSenderConfig` / `TelegramBroadcastMessage` | function / _type_ | the standard `send`: `sendMessage` with a text or `copyMessage` of a prepared message, through the Bot API |
178
181
  | `createTelegramOperatorChannel` | function | the operator chat, apart from the journal: `post(text, topic?)` returns at once and never throws; paced sends into forum topics, 429 honoured, oldest dropped on overflow, `drain(signal)` and `close()` for the way down |
179
- | `TelegramOperatorChannelConfig` / `TelegramOperatorChannel` / `TelegramOperatorMessage` / `TelegramOperatorDrop` / `TelegramChatId` | _type_ | `chatId`, `topics`, `send`, `maxQueued`, `minIntervalMs`, `maxAttempts`, `onDropped`; a drop is `overflow` / `refused` / `attempts` / `closed` with its classification |
182
+ | `TelegramOperatorChannelConfig` / `TelegramOperatorChannel` / `TelegramOperatorMessage` / `TelegramOperatorDrop` / `TelegramChatId` | _type_ | `chatId`, `topics`, `send`, `maxQueued`, `minIntervalMs`, `maxAttempts`, `onDropped`, `sensitivePatterns` (a bot token is always masked); a drop is `overflow` / `refused` / `attempts` / `closed` with its classification |
180
183
  | `telegramOperatorSender` / `TelegramOperatorSenderConfig` | function / _type_ | the standard `send`: `sendMessage` into the chat and topic, plain text unless `parseMode` |
181
184
  | `createTelegramLocalFiles` | function | files the local Bot API server wrote under `<root>/<token>/`: `resolve(file_path)` and `remove(file_path)` only inside the bot's directory, judged on real paths so a link is judged by where it leads; `check()` for readiness. Recommended root variable: `BOT_API_FILES_ROOT` |
182
185
  | `TelegramLocalFilesConfig` / `TelegramLocalFiles` / `TelegramLocalFilesCheck` / `TelegramLocalFileError` / `TelegramLocalFileRefusal` | _type_ / class | `root`, `token`; refusals `root-not-absolute` / `bot-directory-unavailable` / `outside-bot-directory` / `missing` / `not-a-file`, never carrying the path or the token |
package/llms-full.txt CHANGED
@@ -12829,7 +12829,10 @@ const operators = createTelegramOperatorChannel<'users' | 'payments' | 'errors'>
12829
12829
  operators.post(`New user ${user.id}`, 'users')
12830
12830
  ```
12831
12831
 
12832
- What to post stays the bot's. On the way down, drain it within the grace period
12832
+ Every message is masked before it leaves: a bot token always — bots post their
12833
+ errors here, and an error about a local Bot API file carries the token in its
12834
+ path — plus any `sensitivePatterns` the bot adds; `telegramOperatorSender` also
12835
+ masks its own exact token. What to post stays the bot's. On the way down, drain it within the grace period
12833
12836
  and close it: `drain: (context) => operators.drain(context.signal)`,
12834
12837
  `close: () => operators.close()` in a resource.
12835
12838
 
@@ -18540,7 +18543,7 @@ injected grammY bot. → ADR 0143, ADR 0201 — [guide](../guide/telegram.md)
18540
18543
  | `TelegramBroadcastConfig` / `TelegramBroadcastSend` / `TelegramBroadcastReport` / `TelegramBroadcastRunOutcome` / `TelegramBroadcastOutcome` / `TelegramBroadcastRecipient` | _type_ | `name`, `directory`, `recipients()`, `send({ recipient, attempt })`, `maxAttempts`, `signal`, `onProgress`; counts `delivered` / `unreachable` / `failed` / `uncertain` / `pending` and `finished` / `stopped` / `halted` / `dry-run` |
18541
18544
  | `telegramBroadcastSender` / `TelegramBroadcastSenderConfig` / `TelegramBroadcastMessage` | function / _type_ | the standard `send`: `sendMessage` with a text or `copyMessage` of a prepared message, through the Bot API |
18542
18545
  | `createTelegramOperatorChannel` | function | the operator chat, apart from the journal: `post(text, topic?)` returns at once and never throws; paced sends into forum topics, 429 honoured, oldest dropped on overflow, `drain(signal)` and `close()` for the way down |
18543
- | `TelegramOperatorChannelConfig` / `TelegramOperatorChannel` / `TelegramOperatorMessage` / `TelegramOperatorDrop` / `TelegramChatId` | _type_ | `chatId`, `topics`, `send`, `maxQueued`, `minIntervalMs`, `maxAttempts`, `onDropped`; a drop is `overflow` / `refused` / `attempts` / `closed` with its classification |
18546
+ | `TelegramOperatorChannelConfig` / `TelegramOperatorChannel` / `TelegramOperatorMessage` / `TelegramOperatorDrop` / `TelegramChatId` | _type_ | `chatId`, `topics`, `send`, `maxQueued`, `minIntervalMs`, `maxAttempts`, `onDropped`, `sensitivePatterns` (a bot token is always masked); a drop is `overflow` / `refused` / `attempts` / `closed` with its classification |
18544
18547
  | `telegramOperatorSender` / `TelegramOperatorSenderConfig` | function / _type_ | the standard `send`: `sendMessage` into the chat and topic, plain text unless `parseMode` |
18545
18548
  | `createTelegramLocalFiles` | function | files the local Bot API server wrote under `<root>/<token>/`: `resolve(file_path)` and `remove(file_path)` only inside the bot's directory, judged on real paths so a link is judged by where it leads; `check()` for readiness. Recommended root variable: `BOT_API_FILES_ROOT` |
18546
18549
  | `TelegramLocalFilesConfig` / `TelegramLocalFiles` / `TelegramLocalFilesCheck` / `TelegramLocalFileError` / `TelegramLocalFileRefusal` | _type_ / class | `root`, `token`; refusals `root-not-absolute` / `bot-directory-unavailable` / `outside-bot-directory` / `missing` / `not-a-file`, never carrying the path or the token |
package/llms.txt CHANGED
@@ -39,7 +39,7 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
39
39
  - **stitchkit/testing**: [llms/testing.txt](llms/testing.txt) 30.8 KB
40
40
  - **stitchkit/agent-runtime/sandbox**: [llms/agent-runtime-sandbox.txt](llms/agent-runtime-sandbox.txt) 8.1 KB
41
41
  - **stitchkit/files**: [llms/files.txt](llms/files.txt) 2.9 KB
42
- - **stitchkit/telegram**: [llms/telegram.txt](llms/telegram.txt) 11.7 KB
42
+ - **stitchkit/telegram**: [llms/telegram.txt](llms/telegram.txt) 12.0 KB
43
43
  - **stitchkit/tracking**: [llms/tracking.txt](llms/tracking.txt) 16.8 KB
44
44
  - **stitchkit/release**: [llms/release.txt](llms/release.txt) 8.9 KB
45
45
  - **stitchkit/tracking/server**: [llms/tracking-server.txt](llms/tracking-server.txt) 2.5 KB
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.96.0",
3
+ "version": "0.96.1",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",