stitchkit 0.95.2 → 0.96.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 (59) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/dist/agent-runtime/runtime-internals.d.ts +6 -0
  3. package/dist/agent-runtime/runtime-internals.d.ts.map +1 -1
  4. package/dist/application/grammy-bot.d.ts +44 -0
  5. package/dist/application/grammy-bot.d.ts.map +1 -0
  6. package/dist/application/grammy-update-admission.d.ts +35 -0
  7. package/dist/application/grammy-update-admission.d.ts.map +1 -0
  8. package/dist/application/grammy.d.ts +23 -4
  9. package/dist/application/grammy.d.ts.map +1 -1
  10. package/dist/application/kernel-admission.d.ts +11 -0
  11. package/dist/application/kernel-admission.d.ts.map +1 -1
  12. package/dist/application/kernel-resource-context.d.ts.map +1 -1
  13. package/dist/application/resource.d.ts +22 -0
  14. package/dist/application/resource.d.ts.map +1 -1
  15. package/dist/entrypoints/agent-runtime/harness.js +1 -1
  16. package/dist/entrypoints/agent-runtime.js +1 -1
  17. package/dist/entrypoints/application/grammy.d.ts +2 -1
  18. package/dist/entrypoints/application/grammy.d.ts.map +1 -1
  19. package/dist/entrypoints/application/grammy.js +139 -5
  20. package/dist/entrypoints/application.d.ts +1 -1
  21. package/dist/entrypoints/application.d.ts.map +1 -1
  22. package/dist/entrypoints/application.js +1 -1
  23. package/dist/entrypoints/live.js +6 -1
  24. package/dist/entrypoints/observability.d.ts +1 -0
  25. package/dist/entrypoints/observability.d.ts.map +1 -1
  26. package/dist/entrypoints/observability.js +46 -4
  27. package/dist/entrypoints/telegram.d.ts +4 -0
  28. package/dist/entrypoints/telegram.d.ts.map +1 -1
  29. package/dist/entrypoints/telegram.js +680 -88
  30. package/dist/entrypoints/testing.js +1 -1
  31. package/dist/{index-w906953s.js → index-5qn1kx34.js} +48 -0
  32. package/dist/{index-8zja4pkf.js → index-nts4a2br.js} +5 -1
  33. package/dist/live/watch-client.d.ts.map +1 -1
  34. package/dist/observability/json-logger.d.ts +38 -0
  35. package/dist/observability/json-logger.d.ts.map +1 -0
  36. package/dist/telegram/bot-api.d.ts +50 -0
  37. package/dist/telegram/bot-api.d.ts.map +1 -0
  38. package/dist/telegram/broadcast-state.d.ts +67 -0
  39. package/dist/telegram/broadcast-state.d.ts.map +1 -0
  40. package/dist/telegram/broadcast.d.ts +93 -0
  41. package/dist/telegram/broadcast.d.ts.map +1 -0
  42. package/dist/telegram/local-files.d.ts +63 -0
  43. package/dist/telegram/local-files.d.ts.map +1 -0
  44. package/dist/telegram/operator-channel.d.ts +72 -0
  45. package/dist/telegram/operator-channel.d.ts.map +1 -0
  46. package/llms/application-grammy.txt +5 -0
  47. package/llms/application.2.txt +1 -0
  48. package/llms/application.txt +51 -13
  49. package/llms/observability.txt +5 -0
  50. package/llms/server.4.txt +3 -0
  51. package/llms/stitchkit.txt +5 -5
  52. package/llms/telegram.txt +159 -3
  53. package/llms/{upgrading-0.37.0-0.53.0.txt → upgrading-0.37.0-0.56.0.txt} +130 -3
  54. package/llms/{upgrading-0.55.0-0.68.0.txt → upgrading-0.57.0-0.69.0.txt} +44 -130
  55. package/llms/{upgrading-0.69.0-0.95.0.txt → upgrading-0.70.0-0.96.0.txt} +40 -44
  56. package/llms/upgrading.txt +3 -3
  57. package/llms-full.txt +265 -23
  58. package/llms.txt +10 -9
  59. package/package.json +1 -1
@@ -5,6 +5,9 @@
5
5
 
6
6
  Contains: Guide: Observability (docs/guide/observability.md) · API reference: `stitchkit/observability` (docs/api/reference.md)
7
7
 
8
+ Also read:
9
+ - see `llms/telegram.txt` for the Telegram bots guide
10
+
8
11
  ==============================================================================
9
12
  # Guide: Observability (docs/guide/observability.md)
10
13
  ==============================================================================
@@ -831,6 +834,8 @@ audit event. See the [Observability guide](../guide/observability.md).
831
834
  | `DimensionCollision` / `SetRequestDimensionsOptions` | _type_ | explicit overwrite, preserve or error policy for dimension keys |
832
835
  | `createBoundedLogger` | function | decorate a `StitchLogger` with request context, shared sanitisation, redaction and total bounds |
833
836
  | `DEFAULT_REDACT_PATHS` | constant | baseline credential/token paths added to the sanitizer's sensitive-key policy |
837
+ | `createJsonLogger` | function | the process journal: one JSON line per call in pino's shape (numeric `level`, epoch `time`, `msg`) on standard output, through `createBoundedLogger` — an `Error` in any field keeps name, message, stack and cause; secrets masked; `level` threshold, static `fields`, injectable `write` |
838
+ | `JsonLoggerOptions` / `JsonLogLevel` / `JSON_LOG_LEVELS` | _type_ / constant | the bounded-logger options plus `level`, `fields`, `write`, `now`; pino's numbers `debug` 20 … `error` 50, which a line reader matches on |
834
839
  | `BoundedLoggerBounds` / `BoundedLoggerOptions` | _type_ | per-value and total record ceilings (`stringLengthByKey` raises the string bound for named keys such as `stack`) plus sink/redaction configuration |
835
840
  | `RequestEvent` | _type_ | the normalised audit event handed to the sink; opt-in HTTP cancellation rows carry `outcome: 'cancelled'` |
836
841
  | `runUnitOfWork` / `RunUnitOfWorkOptions` | function / _type_ | run work that did not arrive over a transport — an agent loop, a scheduled broadcast — inside one context, with the same completion record a request gets |
package/llms/server.4.txt CHANGED
@@ -386,6 +386,9 @@ and our payload is wrong, which is the case a list of substrings quietly counts
386
386
  against the user. An unrecognised refusal leaves the recipient reachable: losing
387
387
  a working subscriber forever costs more than one wasted send.
388
388
 
389
+ The rest of what a bot is assembled from — its resources, journal, operator
390
+ channel, broadcasts and local Bot API files — is in [Telegram bots](telegram.md).
391
+
389
392
  ## Cookies
390
393
 
391
394
  ```ts
@@ -69,7 +69,7 @@ own, recorded as an ADR.
69
69
  | `stitchkit/cli` | server | stable | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
70
70
  | `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
71
71
  | `stitchkit/files` | server (Bun or Node) | stable | peer-free managed local-file boundary |
72
- | `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification and Bot API send-failure classification |
72
+ | `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification, send-failure classification, resumable broadcasts, the operator channel and local Bot API files |
73
73
  | `stitchkit/tracking` | browser **and** server | evolving | visitor-tracking mechanics — `createTrackingClient`, the tab-shared outbox, the page-leave beacon, attribution, the contract factory; no event vocabulary, no React |
74
74
  | `stitchkit/tracking/server` | server (Bun or Node) | evolving | the decisions a tracking backend makes — dispositions, visit lease over an application-owned store, active intervals, presence; no database |
75
75
  | `stitchkit/release` | browser **and** server | evolving | a page follows the release it was built for — `createReleaseMarker` on the server, `createReleaseWatcher` in the browser, the `X-Build-Id` header and a socket event between them |
@@ -77,11 +77,11 @@ own, recorded as an ADR.
77
77
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
78
78
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
79
79
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
80
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 40 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
80
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 41 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
81
81
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the managed-resource conformance kit |
82
82
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
83
83
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
84
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 21 of the 40 minors since 0.56.2, most recently 0.94.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
84
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 21 of the 41 minors since 0.56.2, most recently 0.94.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
85
85
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
86
86
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
87
87
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -91,8 +91,8 @@ own, recorded as an ADR.
91
91
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
92
92
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
93
93
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
94
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 8 of the 40 minors since 0.56.2, most recently 0.95.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
95
- | `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
94
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 9 of the 41 minors since 0.56.2, most recently 0.96.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
95
+ | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
96
96
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
97
97
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
98
98
  | `stitchkit/application/diagnostic-journal` | server | evolving | the local diagnostic journal — the one part of the kernel that spawns, locks and writes files |
package/llms/telegram.txt CHANGED
@@ -3,7 +3,152 @@
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: API reference: `stitchkit/telegram` (docs/api/reference.md)
6
+ Contains: Guide: Telegram bots (docs/guide/telegram.md) · API reference: `stitchkit/telegram` (docs/api/reference.md)
7
+
8
+ ==============================================================================
9
+ # Guide: Telegram bots (docs/guide/telegram.md)
10
+ ==============================================================================
11
+
12
+ # Telegram bots
13
+
14
+ `stitchkit/telegram` holds what the Bot API itself asks of every bot, with no
15
+ bot library; `stitchkit/application/grammy` makes a grammY bot a set of
16
+ application resources. Together they are what a bot is assembled from, so that
17
+ a bot's own code is its product and nothing else. A generated bot to start from:
18
+ `bun create stitchkit my-bot --template telegram-bot`. Why it is shaped this
19
+ way: [ADR 0201](../decisions/0201-a-telegram-bot-is-assembled-from-primitives.md).
20
+
21
+ | Job | Where |
22
+ |---|---|
23
+ | the bot as resources: command menu, then long polling with updates admitted by batch | `grammyBotResources` — [application kernel](application-kernel.md#optional-grammy-adapter) |
24
+ | the process journal | `createJsonLogger` from `stitchkit/observability` |
25
+ | the operator's chat: product events, apart from the journal | `createTelegramOperatorChannel` |
26
+ | a broadcast that survives crashes, deploys and Ctrl-C | `runTelegramBroadcast` |
27
+ | a file the local Bot API server wrote to disk | `createTelegramLocalFiles` |
28
+ | why a send was refused | `classifyTelegramSendFailure` — [auth and errors](auth-and-errors.md#telegram-mini-apps) |
29
+ | Mini App `initData` | `verifyTelegramInitData` — [auth and errors](auth-and-errors.md#telegram-mini-apps) |
30
+ | one Bot API call without a bot library | `callTelegramBotApi` |
31
+
32
+ ## The journal
33
+
34
+ A bot's journal is `createJsonLogger`: one JSON object per line on standard
35
+ output, in pino's shape — numeric `level` (`debug` 20, `info` 30, `warn` 40,
36
+ `error` 50), `time` in epoch milliseconds, `msg` — so `pino-pretty` reads it on
37
+ a terminal and a supervisor matching `"level":50` sees errors. Unlike pino, an
38
+ `Error` under *any* key is written with its name, message, stack and cause; an
39
+ error logged as `{ error }` does not become `"error":{}`.
40
+
41
+ ```ts
42
+ import { createJsonLogger } from 'stitchkit/observability'
43
+ import { TELEGRAM_BOT_TOKEN_PATTERN } from 'stitchkit/telegram'
44
+
45
+ export const logger = createJsonLogger({
46
+ level: env.LOG_LEVEL,
47
+ fields: { service: 'my-bot' },
48
+ sensitiveUrlPatterns: [TELEGRAM_BOT_TOKEN_PATTERN],
49
+ })
50
+
51
+ logger.error('Application startup failed', { error })
52
+ ```
53
+
54
+ The error line contract, for whatever recognises a failure from the journal: a
55
+ line whose `level` is `50` or more is an error. There is no separate `fatal`;
56
+ an unrecoverable end is an `error` line followed by a non-zero exit.
57
+
58
+ ## The operator channel
59
+
60
+ The chat where people read about new users, payments and failures is not the
61
+ journal. The journal must not lose a line to a slow network; the chat must not
62
+ slow the bot down. `post` returns at once and never throws; sends are paced
63
+ below Telegram's per-chat limit (one every 3 s by default), 429s wait the time
64
+ Telegram named, and when the chat cannot keep up the oldest message is dropped
65
+ and reported.
66
+
67
+ ```ts
68
+ import { createTelegramOperatorChannel, telegramOperatorSender } from 'stitchkit/telegram'
69
+
70
+ const operators = createTelegramOperatorChannel<'users' | 'payments' | 'errors'>({
71
+ chatId: env.OPERATOR_CHAT_ID,
72
+ topics: { users: 2, payments: 3, errors: 4 }, // forum message_thread_id
73
+ send: telegramOperatorSender({ token: env.BOT_TOKEN }),
74
+ onDropped: (drop) => logger.warn('Operator message dropped', { ...drop }),
75
+ })
76
+
77
+ operators.post(`New user ${user.id}`, 'users')
78
+ ```
79
+
80
+ What to post stays the bot's. On the way down, drain it within the grace period
81
+ and close it: `drain: (context) => operators.drain(context.signal)`,
82
+ `close: () => operators.close()` in a resource.
83
+
84
+ ## Broadcasts
85
+
86
+ A broadcast is a job with an end, so it is a function, not a resource:
87
+
88
+ ```ts
89
+ import { runTelegramBroadcast, telegramBroadcastSender } from 'stitchkit/telegram'
90
+
91
+ const report = await runTelegramBroadcast({
92
+ name: '2026-04-28-price-lock',
93
+ directory: env.STATE_DIRECTORY, // a state root, never the release tree
94
+ recipients: () => audience.activeSubscribers(),
95
+ send: telegramBroadcastSender({
96
+ token: env.BOT_TOKEN,
97
+ message: { copyFrom: { chatId: env.DRAFTS_CHAT_ID, messageId: 42 } },
98
+ }),
99
+ signal: stop.signal,
100
+ onProgress: (progress) => logger.info('Broadcast', { ...progress }),
101
+ })
102
+ ```
103
+
104
+ - **Resumable by name.** The audience is written once on the first run; running
105
+ the same name again continues with that list. Progress is an append-only
106
+ journal beside it, one line before and one after each send.
107
+ - **Nobody twice.** A send that was in flight when the process died is recorded
108
+ `uncertain` and not repeated. A signal stops between sends, after the one in
109
+ flight is recorded.
110
+ - **Refusals by meaning.** A blocked, deactivated or never-started recipient is
111
+ `unreachable` and never addressed again. A 429 waits Telegram's
112
+ `retry_after` and sends the same recipient again; a server error is retried
113
+ with backoff up to `maxAttempts`, then `failed`. A message Telegram cannot
114
+ parse is not the recipient's fault: the run **halts** with them still pending,
115
+ so the run after the fix reaches them. So does Telegram being unreachable.
116
+ - **Pacing** is `ratePerSecond`, 25 by default. `dryRun` counts and writes
117
+ nothing. One runner per name: a second concurrent run is refused.
118
+
119
+ The same call works from a script (the process's signal) and inside a live
120
+ application (the application's signal, with admission held by the caller).
121
+ The recipient state contains Telegram ids: keep it in the state directory, out
122
+ of git.
123
+
124
+ ## Files from a local Bot API server
125
+
126
+ A local `telegram-bot-api` keeps downloaded files under `<dir>/<bot token>/`,
127
+ and a bot sharing that directory reads them from disk and deletes them when
128
+ done. `file_path` comes from a server over the network, so it is resolved only
129
+ inside the bot's directory — `../`, an absolute path elsewhere and a link
130
+ pointing out are refused, judged on real paths.
131
+
132
+ ```ts
133
+ import { createTelegramLocalFiles } from 'stitchkit/telegram'
134
+
135
+ const files = createTelegramLocalFiles({ root: env.BOT_API_FILES_ROOT, token: env.BOT_TOKEN })
136
+
137
+ const { file_path } = await ctx.getFile()
138
+ const path = await files.resolve(file_path) // throws TelegramLocalFileError
139
+ // … process it …
140
+ await files.remove(file_path)
141
+ ```
142
+
143
+ The recommended variable for the root is **`BOT_API_FILES_ROOT`** — the
144
+ server's `--dir`, absolute, as the bot's process sees it. A relative `file_path`
145
+ (a server without `--local`) and an absolute one inside the bot's directory (a
146
+ server with `--local`) both resolve. A refusal carries a reason
147
+ (`root-not-absolute`, `bot-directory-unavailable`, `outside-bot-directory`,
148
+ `missing`, `not-a-file`) and never the path or the token. `files.check()`
149
+ answers whether the directory is there to read and delete from; a resource that
150
+ throws on `{ ready: false }` in `start` keeps the bot from answering before it
151
+ can read its media.
7
152
 
8
153
  ==============================================================================
9
154
  # API reference: `stitchkit/telegram` (docs/api/reference.md)
@@ -11,9 +156,10 @@ Contains: API reference: `stitchkit/telegram` (docs/api/reference.md)
11
156
 
12
157
  ## `stitchkit/telegram`
13
158
 
14
- Peer-free server-only Telegram platform primitives. Importing this resolves no
159
+ Peer-free server-only Telegram platform primitives — the Bot API's own
160
+ protocols and the jobs every bot rewrote around them. Importing this resolves no
15
161
  bot library; `stitchkit/application/grammy` remains the lifecycle adapter for an
16
- injected grammY bot. → ADR 0143
162
+ injected grammY bot. → ADR 0143, ADR 0201 — [guide](../guide/telegram.md)
17
163
 
18
164
  | Export | Kind | Summary |
19
165
  |--------|------|---------|
@@ -26,5 +172,15 @@ injected grammY bot. → ADR 0143
26
172
  | `classifyTelegramSendFailure` | function | name a refused Bot API send and separate "retry this send" from "stop addressing this recipient" |
27
173
  | `TelegramSendFailure` | _type_ | reason, `status`, Telegram-stated `retryAfterSeconds`, `retryable`, `recipientUnreachable` and which evidence produced the answer |
28
174
  | `TelegramSendFailureReason` | _type_ | `blocked-by-user` / `user-deactivated` / `chat-not-found` / `not-started` / `rate-limited` / `message-invalid` / `server-error` / `unknown` |
175
+ | `runTelegramBroadcast` | function | a resumable broadcast by `name` under a state `directory`: audience written once, an append-only journal, pacing at `ratePerSecond` (25), 429 waits `retry_after`, unreachable recipients never addressed again, a `message-invalid` or unreachable Telegram halts without charging the recipient, a send in flight at a crash is `uncertain` and not repeated, `dryRun`, one runner per name |
176
+ | `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
+ | `telegramBroadcastSender` / `TelegramBroadcastSenderConfig` / `TelegramBroadcastMessage` | function / _type_ | the standard `send`: `sendMessage` with a text or `copyMessage` of a prepared message, through the Bot API |
178
+ | `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 |
180
+ | `telegramOperatorSender` / `TelegramOperatorSenderConfig` | function / _type_ | the standard `send`: `sendMessage` into the chat and topic, plain text unless `parseMode` |
181
+ | `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
+ | `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 |
183
+ | `callTelegramBotApi` / `TelegramBotApiCall` / `TelegramBotApiError` | function / _type_ / class | one Bot API method over `fetch`; a refusal keeps `error_code`, `description` and `parameters` for the classifier, and no error names the URL |
184
+ | `TELEGRAM_BOT_TOKEN_PATTERN` | constant | the shape of a bot token, for a logger's `sensitiveUrlPatterns` |
29
185
 
30
186
  ---
@@ -1,14 +1,141 @@
1
- # Upgrading 0.37.0 → 0.53.0 — stitchkit docs slice
1
+ # Upgrading 0.37.0 → 0.56.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.37.0 → 0.53.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.37.0 → 0.56.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.37.0 → 0.53.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.37.0 → 0.56.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.56.0
13
+
14
+ ### Surface manifests are version 2
15
+
16
+ `operation.tools` could not describe a role-selected MCP surface, a different
17
+ Agent or CLI selection, or an advertised `extend` schema, so projections moved
18
+ out of the canonical operation:
19
+
20
+ ```ts
21
+ // before
22
+ manifest.operations[0].tools.MCP
23
+ // after
24
+ manifest.toolSurfaces.find((s) => s.transport === 'MCP' && s.surface === null)?.tools
25
+
26
+ // before
27
+ buildSurfaceManifest({ mcpSurfaces: { admin: { services, extend } } })
28
+ // after
29
+ buildSurfaceManifest({ mcpSurfaces: { admin: { services } }, mcpPreparation: { extend } })
30
+ ```
31
+
32
+ A committed snapshot is regenerated once, deliberately: `manifestVersion` is
33
+ `2`, and `ConformanceTransport` gained `REALTIME`, so any exhaustive
34
+ `Record<ConformanceTransport, …>` must handle it.
35
+
36
+ ### `FILE_*` codes joined the error registry
37
+
38
+ `StitchErrorCode` gained `FILE_INVALID_PATH`, `FILE_OUTSIDE_ROOT`,
39
+ `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`, `FILE_INSPECTION_REJECTED`,
40
+ `FILE_TOO_LARGE` and `FILE_EXISTS`. Only exhaustive maps break:
41
+
42
+ ```ts
43
+ // before — compiled while the registry had no file codes
44
+ const copy: Record<StitchErrorCode, string> = { …, RATE_LIMITED: '…' }
45
+ // after — add the seven managed-file codes (or use Partial<Record<…>>)
46
+ ```
47
+
48
+ Unexpected IO stays scrubbed as `INTERNAL_SERVER_ERROR`: the new codes are the
49
+ caller-safe ones only.
50
+
51
+ ### `ScopedAuthHook` is nominal
52
+
53
+ A hand-written function shaped like an auth hook is no longer assignable —
54
+ identity now comes from the factory, so scope ownership and the inferred
55
+ context cannot drift apart:
56
+
57
+ ```ts
58
+ // before — a structural stand-in
59
+ const auth: ScopedAuthHook<Scopes> = async (ctx, endpoint) => { … }
60
+ // after — create it, then compose domains
61
+ const auth = createAuthHook({ resolve, rules })
62
+ const composed = composeAuthHooks({ hooks: [auth, billingAuth], defaultScope: 'public' })
63
+ ```
64
+
65
+ ### Managed-file inspectors also run on reads
66
+
67
+ An inspector is no longer write-only, and it has a finite default deadline
68
+ (15 s). Make it read-aware and idempotent — a read carries no
69
+ `declaredMediaType`:
70
+
71
+ ```ts
72
+ // before
73
+ inspect: ({ declaredMediaType }) => inspectDeclaredType(declaredMediaType!)
74
+ // after
75
+ inspect: ({ prefix, declaredMediaType, signal }) =>
76
+ inspectBytes(prefix, { declaredMediaType, signal })
77
+ ```
78
+
79
+ Set `inspectionTimeoutMs` explicitly when 15 seconds is the wrong budget.
80
+
81
+ ### Direct async-operation binding needs a wire-stable ID
82
+
83
+ A direct binding reuses the start output as the follow-up wire input, so the ID
84
+ schema must parse to itself (`z.input` equals `z.output`, no transform,
85
+ coercion, default or overwrite). Anything else is parsed twice:
86
+
87
+ ```ts
88
+ // before — a transform silently ran on start and again on every follow-up
89
+ defineAsyncOperationContract({ binding: 'direct', id: z.string().transform(Number) })
90
+ // after — keep the wire shape, adapt explicitly
91
+ defineAsyncOperationContract({
92
+ binding: 'adapted',
93
+ id,
94
+ adapters: { idFromStart, inputFor },
95
+ })
96
+ ```
97
+
98
+ ## Released migration: 0.55.0
99
+
100
+ ### Peer-free `implementRemote`
101
+
102
+ `implementRemote` now has one canonical, optional-peer-free owner. This keeps
103
+ MCP SDK and AI SDK modules out of CLI bundles that only proxy HTTP calls:
104
+
105
+ ```ts
106
+ // before
107
+ import { implementRemote } from 'stitchkit/tools'
108
+ // after
109
+ import { implementRemote } from 'stitchkit/remote'
110
+ ```
111
+
112
+ ### Managed file boundary and strict auth returns
113
+
114
+ Create one boundary during application bootstrap and pass the capability, never
115
+ a per-call directory or host path:
116
+
117
+ ```ts
118
+ import { createManagedFileBoundary } from 'stitchkit/files'
119
+
120
+ const files = await createManagedFileBoundary({ root: '/srv/app-files' })
121
+
122
+ // before
123
+ defineDownloadTool({ defaultDir: '/srv/app-files', resolveUrl, ...common })
124
+ defineUploadTool({ upload: (path) => provider.uploadFile(path), ...common })
125
+ defineViewFileTool({ baseDir: '/srv/app-files', ...common })
126
+
127
+ // after
128
+ defineDownloadTool({ files, resolveUrl, ...common })
129
+ defineUploadTool({ files, upload: ({ bytes }) => provider.upload(bytes), ...common })
130
+ defineViewFileTool({ files, ...common })
131
+ ```
132
+
133
+ Downloaded `path` is now relative to the boundary and MIME metadata is named
134
+ `mediaType`. Update raw `mountDownload`/`mountUpload`/`mountViewFile` configs the
135
+ same way. Auth predicates must explicitly return `true`, `false`, or a plain
136
+ object of context fields; replace accidental `undefined` fallthroughs with the
137
+ intended boolean.
138
+
12
139
  ## Released migration: 0.53.0
13
140
 
14
141
  ### Realtime `emit` returns `boolean` instead of `void`
@@ -1,14 +1,55 @@
1
- # Upgrading 0.55.0 → 0.68.0 — stitchkit docs slice
1
+ # Upgrading 0.57.0 → 0.69.0 — stitchkit docs slice
2
2
 
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: Guide: Upgrading, 0.55.0 → 0.68.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.57.0 → 0.69.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.55.0 → 0.68.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.57.0 → 0.69.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.69.0
13
+
14
+ ### Direct coding-tool operation names
15
+
16
+ `createAgentCodingTools` no longer prefixes durable operation identity with `coding_`, and the
17
+ unguarded exact-string edit tool is gone. Update approval maps, presenters and tests together:
18
+
19
+ ```ts
20
+ // before
21
+ { coding_read_file: 'approved', coding_search: 'approved', coding_patch_file: 'user-approval' }
22
+
23
+ // after
24
+ { read_file: 'approved', search_files: 'approved', apply_patch: 'user-approval' }
25
+ ```
26
+
27
+ The full mapping is `coding_read_file → read_file`, `coding_write_file → write_file`,
28
+ `coding_search → search_files`, `coding_patch_file → apply_patch`, `coding_shell → run_command`,
29
+ `coding_read_artifact → read_output` and `harness_read_resource → read_resource`. Replace
30
+ `coding_edit_file` with `read_file` followed by guarded `apply_patch`; `read_file` now returns the
31
+ required SHA-256. If the host declares no executable aliases, expect `run_command` to be absent.
32
+
33
+ ### Durable Agent approval message variants
34
+
35
+ If a renderer, store adapter or export pipeline exhaustively switches over
36
+ `AgentMessage.role`, add the `tool` branch. If it switches over
37
+ `AgentMessagePart.type`, add `tool-approval-request` and `tool-approval-response`.
38
+ They are durable provider-continuation evidence: preserve them in storage and provider history;
39
+ a UI may render them as approval state or intentionally omit their visual row.
40
+
41
+ ```ts
42
+ // before
43
+ const unreachable: never = message.role
44
+
45
+ // after
46
+ if (message.role === 'tool') renderToolContinuation(message.parts)
47
+ else renderExistingRole(message)
48
+ ```
49
+
50
+ No data migration is needed. The schema still accepts all earlier records, and applications that
51
+ do not exhaustively branch over these unions compile unchanged.
52
+
12
53
  ## Released migration: 0.68.0
13
54
 
14
55
  ### Make Unix transport selection explicit outside Bun
@@ -1073,130 +1114,3 @@ The consequence for an exhaustive switch written without a `default` branch is a
1073
1114
  compile error, which is the point. The consequence for a switch that *has* a
1074
1115
  `default` is worse and silent: post-commit admissions fall into it and are
1075
1116
  projected as an unknown event. Grep for publisher switches before upgrading.
1076
-
1077
- ## Released migration: 0.56.0
1078
-
1079
- ### Surface manifests are version 2
1080
-
1081
- `operation.tools` could not describe a role-selected MCP surface, a different
1082
- Agent or CLI selection, or an advertised `extend` schema, so projections moved
1083
- out of the canonical operation:
1084
-
1085
- ```ts
1086
- // before
1087
- manifest.operations[0].tools.MCP
1088
- // after
1089
- manifest.toolSurfaces.find((s) => s.transport === 'MCP' && s.surface === null)?.tools
1090
-
1091
- // before
1092
- buildSurfaceManifest({ mcpSurfaces: { admin: { services, extend } } })
1093
- // after
1094
- buildSurfaceManifest({ mcpSurfaces: { admin: { services } }, mcpPreparation: { extend } })
1095
- ```
1096
-
1097
- A committed snapshot is regenerated once, deliberately: `manifestVersion` is
1098
- `2`, and `ConformanceTransport` gained `REALTIME`, so any exhaustive
1099
- `Record<ConformanceTransport, …>` must handle it.
1100
-
1101
- ### `FILE_*` codes joined the error registry
1102
-
1103
- `StitchErrorCode` gained `FILE_INVALID_PATH`, `FILE_OUTSIDE_ROOT`,
1104
- `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`, `FILE_INSPECTION_REJECTED`,
1105
- `FILE_TOO_LARGE` and `FILE_EXISTS`. Only exhaustive maps break:
1106
-
1107
- ```ts
1108
- // before — compiled while the registry had no file codes
1109
- const copy: Record<StitchErrorCode, string> = { …, RATE_LIMITED: '…' }
1110
- // after — add the seven managed-file codes (or use Partial<Record<…>>)
1111
- ```
1112
-
1113
- Unexpected IO stays scrubbed as `INTERNAL_SERVER_ERROR`: the new codes are the
1114
- caller-safe ones only.
1115
-
1116
- ### `ScopedAuthHook` is nominal
1117
-
1118
- A hand-written function shaped like an auth hook is no longer assignable —
1119
- identity now comes from the factory, so scope ownership and the inferred
1120
- context cannot drift apart:
1121
-
1122
- ```ts
1123
- // before — a structural stand-in
1124
- const auth: ScopedAuthHook<Scopes> = async (ctx, endpoint) => { … }
1125
- // after — create it, then compose domains
1126
- const auth = createAuthHook({ resolve, rules })
1127
- const composed = composeAuthHooks({ hooks: [auth, billingAuth], defaultScope: 'public' })
1128
- ```
1129
-
1130
- ### Managed-file inspectors also run on reads
1131
-
1132
- An inspector is no longer write-only, and it has a finite default deadline
1133
- (15 s). Make it read-aware and idempotent — a read carries no
1134
- `declaredMediaType`:
1135
-
1136
- ```ts
1137
- // before
1138
- inspect: ({ declaredMediaType }) => inspectDeclaredType(declaredMediaType!)
1139
- // after
1140
- inspect: ({ prefix, declaredMediaType, signal }) =>
1141
- inspectBytes(prefix, { declaredMediaType, signal })
1142
- ```
1143
-
1144
- Set `inspectionTimeoutMs` explicitly when 15 seconds is the wrong budget.
1145
-
1146
- ### Direct async-operation binding needs a wire-stable ID
1147
-
1148
- A direct binding reuses the start output as the follow-up wire input, so the ID
1149
- schema must parse to itself (`z.input` equals `z.output`, no transform,
1150
- coercion, default or overwrite). Anything else is parsed twice:
1151
-
1152
- ```ts
1153
- // before — a transform silently ran on start and again on every follow-up
1154
- defineAsyncOperationContract({ binding: 'direct', id: z.string().transform(Number) })
1155
- // after — keep the wire shape, adapt explicitly
1156
- defineAsyncOperationContract({
1157
- binding: 'adapted',
1158
- id,
1159
- adapters: { idFromStart, inputFor },
1160
- })
1161
- ```
1162
-
1163
- ## Released migration: 0.55.0
1164
-
1165
- ### Peer-free `implementRemote`
1166
-
1167
- `implementRemote` now has one canonical, optional-peer-free owner. This keeps
1168
- MCP SDK and AI SDK modules out of CLI bundles that only proxy HTTP calls:
1169
-
1170
- ```ts
1171
- // before
1172
- import { implementRemote } from 'stitchkit/tools'
1173
- // after
1174
- import { implementRemote } from 'stitchkit/remote'
1175
- ```
1176
-
1177
- ### Managed file boundary and strict auth returns
1178
-
1179
- Create one boundary during application bootstrap and pass the capability, never
1180
- a per-call directory or host path:
1181
-
1182
- ```ts
1183
- import { createManagedFileBoundary } from 'stitchkit/files'
1184
-
1185
- const files = await createManagedFileBoundary({ root: '/srv/app-files' })
1186
-
1187
- // before
1188
- defineDownloadTool({ defaultDir: '/srv/app-files', resolveUrl, ...common })
1189
- defineUploadTool({ upload: (path) => provider.uploadFile(path), ...common })
1190
- defineViewFileTool({ baseDir: '/srv/app-files', ...common })
1191
-
1192
- // after
1193
- defineDownloadTool({ files, resolveUrl, ...common })
1194
- defineUploadTool({ files, upload: ({ bytes }) => provider.upload(bytes), ...common })
1195
- defineViewFileTool({ files, ...common })
1196
- ```
1197
-
1198
- Downloaded `path` is now relative to the boundary and MIME metadata is named
1199
- `mediaType`. Update raw `mountDownload`/`mountUpload`/`mountViewFile` configs the
1200
- same way. Auth predicates must explicitly return `true`, `false`, or a plain
1201
- object of context fields; replace accidental `undefined` fallthroughs with the
1202
- intended boolean.