stitchkit 0.113.3 → 0.114.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 +42 -0
- package/README.md +1 -1
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/coding-tools.js +1 -1
- package/dist/entrypoints/agent-runtime/harness.js +4 -4
- package/dist/entrypoints/agent-runtime/realtime.js +2 -2
- package/dist/entrypoints/agent-runtime/sandbox.js +3 -3
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +3 -3
- package/dist/entrypoints/agent-runtime/sqlite/node.js +3 -3
- package/dist/entrypoints/agent-runtime/testing.js +4 -4
- package/dist/entrypoints/agent-runtime.js +12 -12
- package/dist/entrypoints/application/directory-inbox.js +3 -3
- package/dist/entrypoints/application.js +4 -4
- package/dist/entrypoints/cli/publish.js +1 -1
- package/dist/entrypoints/cli.js +4 -4
- package/dist/entrypoints/contract.js +2 -2
- package/dist/entrypoints/http.js +2 -2
- package/dist/entrypoints/index.js +5 -5
- package/dist/entrypoints/node.js +3 -3
- package/dist/entrypoints/observability.js +3 -3
- package/dist/entrypoints/react/keyboard/testing.d.ts +2 -0
- package/dist/entrypoints/react/keyboard/testing.d.ts.map +1 -0
- package/dist/entrypoints/react/keyboard/testing.js +127 -0
- package/dist/entrypoints/react/keyboard.d.ts +4 -2
- package/dist/entrypoints/react/keyboard.d.ts.map +1 -1
- package/dist/entrypoints/react/keyboard.js +83 -319
- package/dist/entrypoints/react.js +15 -3
- package/dist/entrypoints/realtime.js +1 -1
- package/dist/entrypoints/remote.js +1 -1
- package/dist/entrypoints/server.js +18 -18
- package/dist/entrypoints/telegram/grammy.d.ts +2 -0
- package/dist/entrypoints/telegram/grammy.d.ts.map +1 -0
- package/dist/entrypoints/telegram/grammy.js +50 -0
- package/dist/entrypoints/telegram/screens.js +1 -1
- package/dist/entrypoints/telegram.d.ts +1 -0
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +254 -8
- package/dist/entrypoints/testing.js +3 -3
- package/dist/entrypoints/tools/connections.js +10 -10
- package/dist/entrypoints/tools.js +19 -19
- package/dist/entrypoints/tracking.js +1 -1
- package/dist/{index-xvj9h8yh.js → index-61hve0ez.js} +15 -28
- package/dist/index-6j725ez5.js +25 -0
- package/dist/{index-1csp1hwm.js → index-6p3frkv5.js} +1 -1
- package/dist/{index-fy4av0hq.js → index-9bwcthxw.js} +1 -1
- package/dist/{index-523tq7y4.js → index-a3462dqf.js} +1 -1
- package/dist/{index-506kd458.js → index-aw6zahv1.js} +1 -1
- package/dist/{index-tpwbct8j.js → index-dc9ere5p.js} +1 -1
- package/dist/{index-erv5shjq.js → index-kdnfe0sr.js} +1 -1
- package/dist/{index-k7z3j3hr.js → index-km2qw407.js} +4 -4
- package/dist/{index-7fc0c2np.js → index-n3v695sb.js} +1 -1
- package/dist/index-p02zgk8x.js +351 -0
- package/dist/{index-vtann8bz.js → index-p0rf7sq9.js} +8 -8
- package/dist/{index-aykhzcnf.js → index-qj1sy4ep.js} +1 -1
- package/dist/{index-cxpcdswt.js → index-t65erk4z.js} +1 -1
- package/dist/{index-c5at3ykd.js → index-tsngj2b5.js} +22 -10
- package/dist/react/entity-cache.d.ts +24 -2
- package/dist/react/entity-cache.d.ts.map +1 -1
- package/dist/react/keyboard/hooks.d.ts +41 -10
- package/dist/react/keyboard/hooks.d.ts.map +1 -1
- package/dist/react/keyboard/layers.d.ts +27 -8
- package/dist/react/keyboard/layers.d.ts.map +1 -1
- package/dist/react/keyboard/modality.d.ts +21 -0
- package/dist/react/keyboard/modality.d.ts.map +1 -0
- package/dist/react/keyboard/refs.d.ts +13 -0
- package/dist/react/keyboard/refs.d.ts.map +1 -0
- package/dist/react/keyboard/testing.d.ts +80 -0
- package/dist/react/keyboard/testing.d.ts.map +1 -0
- package/dist/react/keyboard/zone.d.ts +60 -10
- package/dist/react/keyboard/zone.d.ts.map +1 -1
- package/dist/telegram/budget-transformer.d.ts +35 -0
- package/dist/telegram/budget-transformer.d.ts.map +1 -0
- package/dist/telegram/outbound-budget.d.ts +101 -0
- package/dist/telegram/outbound-budget.d.ts.map +1 -0
- package/dist/telegram/send-failure.d.ts +6 -1
- package/dist/telegram/send-failure.d.ts.map +1 -1
- package/llms/react-keyboard-testing.txt +24 -0
- package/llms/react-keyboard.txt +264 -4
- package/llms/react.txt +1 -1
- package/llms/server.3.txt +31 -0
- package/llms/server.4.txt +5 -0
- package/llms/stitchkit.2.txt +118 -0
- package/llms/stitchkit.txt +8 -122
- package/llms/telegram-grammy.txt +22 -0
- package/llms/telegram.txt +76 -2
- package/llms/{upgrading-0.103.13-0.113.0.txt → upgrading-0.104.0-0.114.0.txt} +45 -84
- package/llms/upgrading-0.37.0-0.44.0.txt +444 -0
- package/llms/{upgrading-0.44.0-0.60.0.txt → upgrading-0.44.0-0.61.0.txt} +140 -271
- package/llms/{upgrading-0.61.0-0.74.0.txt → upgrading-0.62.0-0.78.0.txt} +137 -140
- package/llms/{upgrading-0.75.0-0.103.0.txt → upgrading-0.79.0-0.103.13.txt} +84 -137
- package/llms/upgrading.txt +5 -5
- package/llms-full.txt +453 -11
- package/llms.txt +12 -10
- package/package.json +9 -1
- package/llms/upgrading-0.37.0.txt +0 -176
- package/dist/{index-616xh7f2.js → index-4qnzda76.js} +0 -0
- package/dist/{index-fsw1ybf3.js → index-d9hq291n.js} +3 -3
- package/dist/{index-bjcjqj46.js → index-m8a44e64.js} +3 -3
- package/dist/{index-5efrpfmj.js → index-qndsr7f0.js} +3 -3
- package/dist/{index-zktz242w.js → index-s47ac8zt.js} +3 -3
- package/dist/{index-56yx8dxp.js → index-tvtacc9a.js} +3 -3
- /package/dist/{index-v1tswj0z.js → index-txg1npmg.js} +0 -0
package/llms/stitchkit.txt
CHANGED
|
@@ -75,7 +75,8 @@ own, recorded as an ADR.
|
|
|
75
75
|
| `stitchkit/process/owner-loss` | bundled server entry (Bun, POSIX) | evolving | narrow bootstrap that keeps the process runner lazy while carrying its owner-loss guard in a single-file bundle |
|
|
76
76
|
| `stitchkit/files/packaging` | build time (Bun or Node) | evolving | installed native asset graph, integrity, Bun packaging plugin and the artifact loader check; no runtime install policy |
|
|
77
77
|
| `stitchkit/files` | server (Bun or Node) | stable | peer-free managed local-file boundary |
|
|
78
|
-
| `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification, send-failure classification, resumable broadcasts, the operator channel with dedupe, a Bot API transport that separates a request that never left from an unknown outcome, local Bot API files, webhook ownership and durable webhook update intake |
|
|
78
|
+
| `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification, send-failure classification, one outbound budget per bot token with weighted albums and shared 429 pauses, resumable broadcasts, the operator channel with dedupe, a Bot API transport that separates a request that never left from an unknown outcome, local Bot API files, webhook ownership and durable webhook update intake |
|
|
79
|
+
| `stitchkit/telegram/grammy` | server (Bun or Node) | evolving | the token's outbound budget as one grammY client transformer — a 429 that comes back as a response pauses the token, and a cancelled wait is a request that never left |
|
|
79
80
|
| `stitchkit/telegram/html` | browser and server | evolving | Telegram's HTML as a tree — clean any markup to what Telegram accepts, cut long text into valid parts by what the reader sees, check markup before it is sent |
|
|
80
81
|
| `stitchkit/telegram/screens` | server (Bun or Node) | evolving | a bot's menus as declared screens over grammY — typed paths, actions and input, message reconciliation, state in any `StorageAdapter`, a test chat |
|
|
81
82
|
| `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 |
|
|
@@ -85,12 +86,13 @@ own, recorded as an ADR.
|
|
|
85
86
|
| `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
|
|
86
87
|
| `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
|
|
87
88
|
| `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
|
|
88
|
-
| `stitchkit/observability` | server | stable<br>_redefined in 2 of the
|
|
89
|
+
| `stitchkit/observability` | server | stable<br>_redefined in 2 of the 59 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
89
90
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, the managed-resource conformance kit and manual time for schedules and retries |
|
|
90
91
|
| `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
91
92
|
| `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
|
|
92
93
|
| `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
|
|
93
|
-
| `stitchkit/
|
|
94
|
+
| `stitchkit/react/keyboard/testing` | server (Bun or Node) | evolving | the keyboard layers and zones without a DOM — fakes for the window, elements and key events beside the two engines, to test the order of layers, `scope`/`inert` and the edges of a zone in milliseconds |
|
|
95
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 24 of the 59 minors since 0.56.2, most recently 0.113.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
94
96
|
| `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
|
|
95
97
|
| `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 |
|
|
96
98
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
@@ -102,7 +104,7 @@ own, recorded as an ADR.
|
|
|
102
104
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
103
105
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
104
106
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
105
|
-
| `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the
|
|
107
|
+
| `stitchkit/application` | browser + server | evolving<br>_redefined in 14 of the 59 minors since 0.56.2, most recently 0.110.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
|
|
106
108
|
| `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
|
|
107
109
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
108
110
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
@@ -215,6 +217,7 @@ map — feature → packages:
|
|
|
215
217
|
| MCP Apps UI widgets | `@modelcontextprotocol/ext-apps` |
|
|
216
218
|
| React data layer (`stitchkit/react`) | `@tanstack/react-query` `react-query-kit` |
|
|
217
219
|
| Keyboard layers and list zones (`stitchkit/react/keyboard`) | `react` |
|
|
220
|
+
| Keyboard test bed (`stitchkit/react/keyboard/testing`) | — (peer-free) |
|
|
218
221
|
| MaxMind GeoIP (`stitchkit/geo`) | `maxmind` |
|
|
219
222
|
| Browser OAuth client (`stitchkit/oauth`) | — |
|
|
220
223
|
| Google OIDC verifier (`stitchkit/google`) | `google-auth-library` |
|
|
@@ -224,6 +227,7 @@ map — feature → packages:
|
|
|
224
227
|
| grammY lifecycle adapters (`stitchkit/application/grammy`) | `grammy` |
|
|
225
228
|
| Telegram platform primitives (`stitchkit/telegram`) | — (peer-free) |
|
|
226
229
|
| Telegram message markup (`stitchkit/telegram/html`) | — (peer-free) |
|
|
230
|
+
| Telegram grammY client transformer (`stitchkit/telegram/grammy`) | `grammy` (types only; the bot brings it) |
|
|
227
231
|
| Telegram screens (`stitchkit/telegram/screens`) | `grammy` (types only; the bot brings it) |
|
|
228
232
|
| OpenTelemetry gauges (`stitchkit/application/opentelemetry`) | `@opentelemetry/api` |
|
|
229
233
|
|
|
@@ -789,121 +793,3 @@ const tenantApi = createClients({ users, posts }, http, {
|
|
|
789
793
|
Every method now requires `tenantId`, and the callback sees it as a `string`.
|
|
790
794
|
The batch form delegates to the same single-contract client runtime, including
|
|
791
795
|
HTTP exposure filtering, multipart, raw responses and output validation.
|
|
792
|
-
|
|
793
|
-
## Chunked uploads
|
|
794
|
-
|
|
795
|
-
A file larger than one request goes as three calls of the application's own
|
|
796
|
-
contract — open, parts, finish — driven by `uploadInChunks` on the client and
|
|
797
|
-
held by `createChunkSpool` (`stitchkit/files`) on the server. The contract stays
|
|
798
|
-
the application's: its fields, its limits, what happens to the finished file.
|
|
799
|
-
|
|
800
|
-
```ts
|
|
801
|
-
// shared: one part size for both halves
|
|
802
|
-
export const CHUNK_BYTES = 4 * 1024 * 1024
|
|
803
|
-
|
|
804
|
-
// contract
|
|
805
|
-
init: {
|
|
806
|
-
method: 'POST', path: '/uploads', desc: 'Open an upload',
|
|
807
|
-
input: z.object({ uploadId: z.string(), totalBytes: z.number(), chunkCount: z.number(), name: z.string() }),
|
|
808
|
-
output: z.object({ finished: Accepted.optional() }),
|
|
809
|
-
},
|
|
810
|
-
chunk: {
|
|
811
|
-
method: 'PUT', path: '/uploads/:uploadId/chunks/:index', desc: 'Send one part',
|
|
812
|
-
params: z.object({ uploadId: z.string(), index: z.coerce.number() }),
|
|
813
|
-
multipart: { files: { bytes: {} } },
|
|
814
|
-
output: z.object({ state: z.enum(['stored', 'repeated']) }),
|
|
815
|
-
},
|
|
816
|
-
finalize: {
|
|
817
|
-
method: 'POST', path: '/uploads/:uploadId/finalize', desc: 'Assemble the file',
|
|
818
|
-
params: z.object({ uploadId: z.string() }),
|
|
819
|
-
output: Accepted,
|
|
820
|
-
},
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
```ts
|
|
824
|
-
// server
|
|
825
|
-
import { createChunkSpool } from 'stitchkit/files'
|
|
826
|
-
|
|
827
|
-
const spool = createChunkSpool({
|
|
828
|
-
directory: env.UPLOAD_SPOOL_DIR,
|
|
829
|
-
chunkBytes: CHUNK_BYTES,
|
|
830
|
-
maxFileBytes: 1024 * 1024 * 1024,
|
|
831
|
-
meta: z.object({ name: z.string() }),
|
|
832
|
-
})
|
|
833
|
-
|
|
834
|
-
implement(contract, {
|
|
835
|
-
init: async ({ input, ctx }) => {
|
|
836
|
-
const done = await findFinished(input.uploadId) // the app's own record
|
|
837
|
-
if (done) return { finished: done }
|
|
838
|
-
await spool.open({ owner: ctx.user.id, ...input, meta: { name: input.name } })
|
|
839
|
-
return {}
|
|
840
|
-
},
|
|
841
|
-
chunk: async ({ params, files, ctx }) => ({
|
|
842
|
-
state: await spool.put({ owner: ctx.user.id, ...params, bytes: files.bytes }),
|
|
843
|
-
}),
|
|
844
|
-
finalize: async ({ params, ctx }) => {
|
|
845
|
-
const key = { owner: ctx.user.id, uploadId: params.uploadId }
|
|
846
|
-
const file = await spool.assemble(key) // file.stream(), file.chunkPaths, file.meta
|
|
847
|
-
const accepted = await store(file)
|
|
848
|
-
await spool.discard(key)
|
|
849
|
-
return accepted
|
|
850
|
-
},
|
|
851
|
-
})
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
```ts
|
|
855
|
-
// client — Bun, Node, a browser or React Native
|
|
856
|
-
import { uploadInChunks } from 'stitchkit'
|
|
857
|
-
|
|
858
|
-
const accepted = await uploadInChunks({
|
|
859
|
-
file,
|
|
860
|
-
chunkBytes: CHUNK_BYTES,
|
|
861
|
-
signal,
|
|
862
|
-
onProgress: ({ sentBytes, totalBytes }) => draw(sentBytes / totalBytes),
|
|
863
|
-
init: async (start, options) => {
|
|
864
|
-
const opened = await api.uploads.init.withOptions({ ...start, name: file.name }, options)
|
|
865
|
-
return opened.finished ? { finished: opened.finished } : undefined
|
|
866
|
-
},
|
|
867
|
-
chunk: (part, options) => api.uploads.chunk.withOptions(part, options),
|
|
868
|
-
finalize: (upload, options) => api.uploads.finalize.withOptions(upload, options),
|
|
869
|
-
})
|
|
870
|
-
```
|
|
871
|
-
|
|
872
|
-
The rules the two halves share:
|
|
873
|
-
|
|
874
|
-
- **The client mints the upload id** (default `crypto.randomUUID()`, fresh per
|
|
875
|
-
call), so a repeated `init` is the same upload. The spool keys it by `owner`
|
|
876
|
-
too — the same id from another owner is another upload. To resume after the
|
|
877
|
-
app restarts instead of sending the file again, pass a stable `uploadId` —
|
|
878
|
-
the id of the app's own job or attempt.
|
|
879
|
-
- **The file is anything with `size` and `slice(start, end)`** (`ChunkSource`).
|
|
880
|
-
A `Blob` is one; so is a platform file that is not a DOM `Blob`, such as an
|
|
881
|
-
Expo `File`, passed without a cast. A part's `bytes` is what `slice`
|
|
882
|
-
returns, so the part call hands it to the client as the multipart file.
|
|
883
|
-
- **A part is idempotent.** The spool keeps a size+sha256 receipt per index:
|
|
884
|
-
the same bytes again answer `'repeated'`, other bytes `UPLOAD_CONFLICT`
|
|
885
|
-
(409). So the driver repeats `init` and a part on a failure that may pass — no
|
|
886
|
-
answer (network, timeout), `429`, `5xx` — up to `retries` times (default 3)
|
|
887
|
-
with a doubling pause from `retryDelayMs` (default 1 s). A refusal is final.
|
|
888
|
-
- **`finalize` is never repeated.** After it the application owns the file and
|
|
889
|
-
the parts are gone; a lost answer is recovered by `init` returning
|
|
890
|
-
`{ finished }`, which the driver hands back without sending a byte.
|
|
891
|
-
- **Cancellation is checked between parts**, so a cancelled upload never
|
|
892
|
-
reaches `finalize`.
|
|
893
|
-
- **Every part but the last is exactly `chunkBytes`** and `chunkCount` must add
|
|
894
|
-
up; the spool refuses anything else with a 4xx `AppError`
|
|
895
|
-
(`ChunkSpoolErrorCode`), which a handler can let through as the client's
|
|
896
|
-
typed error. An application that maps error codes onto its own must map
|
|
897
|
-
these too — `isChunkSpoolErrorCode` / `CHUNK_SPOOL_ERROR_CODES` from
|
|
898
|
-
`stitchkit/files` — or a refusal reaches its client as an unknown 500.
|
|
899
|
-
- **Progress counts file bytes**, inside a part too — the driver passes
|
|
900
|
-
`onUploadProgress` into the part call, so the [upload
|
|
901
|
-
progress](#upload-progress) route of the client applies. A repeated part
|
|
902
|
-
counts again from its start.
|
|
903
|
-
- The first writer of a part wins across processes: its data file is named by
|
|
904
|
-
its hash and its receipt is published by an exclusive link. `sweep()` removes
|
|
905
|
-
uploads untouched for `staleAfterMs` (default 24 h); run it on a schedule.
|
|
906
|
-
|
|
907
|
-
Part size is a latency decision: every part costs a round trip, so on a
|
|
908
|
-
200 ms link a smaller part buys smoother progress at the price of a slower
|
|
909
|
-
upload.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# stitchkit/telegram/grammy — stitchkit docs slice
|
|
2
|
+
|
|
3
|
+
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
|
+
> `llms.txt` in this package.
|
|
5
|
+
|
|
6
|
+
Contains: API reference: `stitchkit/telegram/grammy` (docs/api/reference.md)
|
|
7
|
+
|
|
8
|
+
==============================================================================
|
|
9
|
+
# API reference: `stitchkit/telegram/grammy` (docs/api/reference.md)
|
|
10
|
+
==============================================================================
|
|
11
|
+
|
|
12
|
+
## `stitchkit/telegram/grammy`
|
|
13
|
+
|
|
14
|
+
The token's outbound budget as one grammY client transformer. Server-only, Bun
|
|
15
|
+
or Node; `grammy` appears in the declarations and is never imported at run time —
|
|
16
|
+
the bot brings it. → ADR 0274 — [guide](../guide/telegram.md#one-token-one-outbound-budget)
|
|
17
|
+
|
|
18
|
+
| Export | Kind | Summary |
|
|
19
|
+
|--------|------|---------|
|
|
20
|
+
| `telegramBudgetTransformer` | function | `bot.api.config.use(telegramBudgetTransformer(budget))`: every Bot API call runs under the token's `telegramOutboundBudget`; a 429 that comes back as a response pauses the token, a cancellation before the request was handed on throws `TelegramNotDispatchedError`, a cancellation in flight stays the client's own error; it never retries |
|
|
21
|
+
|
|
22
|
+
---
|
package/llms/telegram.txt
CHANGED
|
@@ -86,6 +86,79 @@ The error line contract, for whatever recognises a failure from the journal: a
|
|
|
86
86
|
line whose `level` is `50` or more is an error. There is no separate `fatal`;
|
|
87
87
|
an unrecoverable end is an `error` line followed by a non-zero exit.
|
|
88
88
|
|
|
89
|
+
## One token, one outbound budget
|
|
90
|
+
|
|
91
|
+
Telegram counts a bot's bulk messages per token, not per campaign, handler or
|
|
92
|
+
module. `telegramOutboundBudget` is that budget once: every alias of one token
|
|
93
|
+
in the process gets the same object, another token gets another, and every send
|
|
94
|
+
through it spends its weight. → [ADR 0274](../decisions/0274-a-telegram-token-has-one-outbound-budget.md).
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { telegramOutboundBudget } from 'stitchkit/telegram'
|
|
98
|
+
import { telegramBudgetTransformer } from 'stitchkit/telegram/grammy'
|
|
99
|
+
|
|
100
|
+
const budget = telegramOutboundBudget({
|
|
101
|
+
token: env.BOT_TOKEN,
|
|
102
|
+
messagesPerSecond: 24,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// The one transformer of the bot's client. No second limiter, no second
|
|
106
|
+
// scheduler: campaigns, replies and every other send of this token share it.
|
|
107
|
+
bot.api.config.use(telegramBudgetTransformer(budget))
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`telegramBudgetTransformer` is the whole wiring for grammY. Without it the
|
|
111
|
+
adapter is `budget.run(method, payload, signal, () => previous(method, payload, signal))`
|
|
112
|
+
inside your own `config.use` closure; that works too, but grammY types its signal
|
|
113
|
+
as the `abort-controller` polyfill's, so the closure has to pass `undefined` or
|
|
114
|
+
cast, and a send cancelled in the queue then waits for its slot and fails as
|
|
115
|
+
`unknown`. The transformer takes grammY's signal as it is.
|
|
116
|
+
|
|
117
|
+
- **The token is the identity, not the sender.** Two bot objects built from one
|
|
118
|
+
token share the budget; two different tokens do not. The registry keeps a
|
|
119
|
+
128-bit identity, never the credential, and `stats()` contains no token.
|
|
120
|
+
- **A message is the unit.** `sendMediaGroup` and `sendPaidMedia` cost their
|
|
121
|
+
media length, `copyMessages` and `forwardMessages` cost their message ids, and
|
|
122
|
+
a follow-up call to action after an album costs one more. Reads and edits
|
|
123
|
+
(`getMe`, `getFile`, `editMessage*`, `deleteMessage`, `sendChatAction`) do not
|
|
124
|
+
spend the budget.
|
|
125
|
+
- **The first send after idle is immediate; the rest are spaced.** This is a
|
|
126
|
+
strict budget, not a bucket that saves a burst: at 24/s the next send is due
|
|
127
|
+
one interval later. A 429 pauses the whole token; when it resumes, one send
|
|
128
|
+
is released at the deadline and the queue continues one interval at a time,
|
|
129
|
+
never as a stampede.
|
|
130
|
+
- **It paces; it does not retry.** `run` calls the send once. A 429 pauses the
|
|
131
|
+
token for Telegram's `retry_after` (or `retryAfterFallbackMs` when Telegram
|
|
132
|
+
gave none) and rethrows; the caller's retry policy decides what happens next.
|
|
133
|
+
The refusal may be thrown or returned: a client transformer sits below the
|
|
134
|
+
place where grammY turns Telegram's `{ ok: false, error_code: 429, … }` body into
|
|
135
|
+
a `GrammyError`, so `run` reads that body too and hands it back unchanged.
|
|
136
|
+
A 429 from an edit or a read is still a fact about the token and pauses later
|
|
137
|
+
sends too. A network failure is rethrown unchanged and stays `unknown`; a 5xx
|
|
138
|
+
is rethrown unchanged and stays `server-error`. Neither is retried by the
|
|
139
|
+
budget or pauses the token — repeating a send whose outcome is unknown is a
|
|
140
|
+
decision, not a primitive's silent guess.
|
|
141
|
+
- **A wait can be cancelled.** `run`/`acquire` take an `AbortSignal`; a
|
|
142
|
+
cancelled waiter spends no slot and calls no provider. Through
|
|
143
|
+
`telegramBudgetTransformer` a send cancelled before it was handed to the client
|
|
144
|
+
throws `TelegramNotDispatchedError` (`classifyTelegramSendFailure` says
|
|
145
|
+
`not-dispatched`, safe to repeat); a send cancelled after it was handed on is
|
|
146
|
+
the client's own error — not evidence that Telegram did not accept it — so
|
|
147
|
+
keep the outcome unknown rather than retrying it as a definite failure.
|
|
148
|
+
- **Non-send retries wait without spending.** An edit or delete retry that got
|
|
149
|
+
a 429 first records the pause with `budget.pauseAfter(error)` (the `run` path
|
|
150
|
+
does this itself), then uses `await budget.waitUntilResumed(signal)`; it shares
|
|
151
|
+
the token's pause but does not take a message slot.
|
|
152
|
+
|
|
153
|
+
`telegramBroadcastSender` and `telegramOperatorSender` still send through
|
|
154
|
+
`callTelegramBotApi`; they are not a second budget. Their returned function does
|
|
155
|
+
not expose the method and payload, so a product that uses one wraps the concrete
|
|
156
|
+
`sendMessage`/`copyMessage` call in `budget.run` where it knows both, or passes
|
|
157
|
+
its own sender. There is no generic wrapper around the opaque sender.
|
|
158
|
+
`runTelegramBroadcast` keeps its per-run `ratePerSecond` and the operator
|
|
159
|
+
channel keeps its per-chat `minIntervalMs`; those are caps of their own jobs,
|
|
160
|
+
not another token quota. The token budget is the binding shared cap.
|
|
161
|
+
|
|
89
162
|
## The operator channel
|
|
90
163
|
|
|
91
164
|
The chat where people read about new users, payments and failures is not the
|
|
@@ -519,7 +592,7 @@ can read its media.
|
|
|
519
592
|
Peer-free server-only Telegram platform primitives — the Bot API's own
|
|
520
593
|
protocols and the jobs every bot rewrote around them. Importing this resolves no
|
|
521
594
|
bot library; `stitchkit/application/grammy` remains the lifecycle adapter for an
|
|
522
|
-
injected grammY bot. → ADR 0143, ADR 0201 — [guide](../guide/telegram.md)
|
|
595
|
+
injected grammY bot. → ADR 0143, ADR 0201, ADR 0274 — [guide](../guide/telegram.md)
|
|
523
596
|
|
|
524
597
|
| Export | Kind | Summary |
|
|
525
598
|
|--------|------|---------|
|
|
@@ -534,7 +607,8 @@ injected grammY bot. → ADR 0143, ADR 0201 — [guide](../guide/telegram.md)
|
|
|
534
607
|
| `createTelegramBotTransport` / `TelegramBotTransportOptions` | function / _type_ | a `fetch` for the Bot API senders that connects before it writes, so a failure before the first byte is `TelegramNotDispatchedError`; connection attempt and budget, answer timeout and size, injectable resolver and socket factory — [guide](../guide/telegram.md#a-transport-that-knows-whether-a-request-left) |
|
|
535
608
|
| `TelegramFetch` / `TelegramBotTransportOpen` / `TelegramBotTransportResolve` / `TelegramBotTransportSocket` | _types_ | the `(url, init) => Promise<Response>` the senders take; the transport's socket factory, resolver and the structural socket it needs (a Node `net.Socket` or `tls.TLSSocket` fits) |
|
|
536
609
|
| `TelegramNotDispatchedError` / `TelegramNotDispatchedStage` | class / _type_ | a request that provably never left (`lookup`, `connect` or `request`), so repeating it cannot duplicate a message |
|
|
537
|
-
| `TelegramSendFailureReason` / `TELEGRAM_SEND_FAILURE_REASONS` | _type_ / constant | `blocked-by-user` / `user-deactivated` / `chat-not-found` / `not-started` / `rate-limited` / `message-invalid` / `server-error` / `not-dispatched` / `unknown`, and the same as a list; a `Record<TelegramSendFailureReason, …>` over them fails to compile when a reason is added |
|
|
610
|
+
| `TelegramSendFailureReason` / `TELEGRAM_SEND_FAILURE_REASONS` | _type_ / constant | `blocked-by-user` / `user-deactivated` / `chat-not-found` / `not-started` / `bot-to-bot-disabled` / `rate-limited` / `message-invalid` / `server-error` / `not-dispatched` / `unknown`, and the same as a list; a `Record<TelegramSendFailureReason, …>` over them fails to compile when a reason is added |
|
|
611
|
+
| `telegramOutboundBudget` / `TelegramOutboundBudget` / `TelegramOutboundBudgetConfig` / `TelegramOutboundBudgetStats` | function / _type_ | one token-wide outbound budget: one object per token in the process, strict 24 messages/s by default, weighted albums and multi-message copies, `run(method, payload, signal, send)` as the transformer adapter (a 429 pauses the token whether `send` throws it or resolves with the Bot API's `{ ok: false, … }` body), `pause`/`pauseAfter` for 429, `waitUntilResumed` for non-send retries and `stats`; it paces and never retries, and no public value carries the token |
|
|
538
612
|
| `runTelegramBroadcast` | function | a resumable broadcast by `name` under a state `directory`: audience and journal durably recorded, pacing at `ratePerSecond` (25), exact retry delays within the finite declared budget, injected classifier, uncertain send outcomes never replayed, `dryRun`, one fenced runner per name |
|
|
539
613
|
| `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` |
|
|
540
614
|
| `TelegramBroadcastFailureSchema` / `TelegramBroadcastFailure` | schema / _type_ | injected `classify` answers retry-after/transient/permanent/ambiguous; exact provider delay is bounded by `maxRetryDelayMs` without shortening, unknown outcome remains uncertain and is never replayed |
|
|
@@ -1,14 +1,56 @@
|
|
|
1
|
-
# Upgrading 0.
|
|
1
|
+
# Upgrading 0.104.0 → 0.114.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.
|
|
6
|
+
Contains: Guide: Upgrading, 0.104.0 → 0.114.0 (docs/guide/upgrading.md)
|
|
7
7
|
|
|
8
8
|
==============================================================================
|
|
9
|
-
# Guide: Upgrading, 0.
|
|
9
|
+
# Guide: Upgrading, 0.104.0 → 0.114.0 (docs/guide/upgrading.md)
|
|
10
10
|
==============================================================================
|
|
11
11
|
|
|
12
|
+
## Released migration: 0.114.0
|
|
13
|
+
|
|
14
|
+
**Who must act:** code with an exhaustive `switch` or `Record` over
|
|
15
|
+
`TelegramSendFailureReason` or `TELEGRAM_SEND_FAILURE_REASONS`. Code that only
|
|
16
|
+
reads `retryable` and `recipientUnreachable` needs no change.
|
|
17
|
+
|
|
18
|
+
`classifyTelegramSendFailure` now returns `reason: 'bot-to-bot-disabled'` for a
|
|
19
|
+
`400 USER_BOT_TO_BOT_DISABLED` ([ADR 0274](../decisions/0274-a-telegram-token-has-one-outbound-budget.md)).
|
|
20
|
+
It is permanent for that send, so it is not retried; it is **not**
|
|
21
|
+
`recipientUnreachable`, because Telegram disables bot-to-bot messaging when
|
|
22
|
+
either bot has not enabled its Bot to Bot setting, and the missing setting may
|
|
23
|
+
be the sender's.
|
|
24
|
+
|
|
25
|
+
1. Add the reason to every exhaustive mapping:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// before
|
|
29
|
+
const code: Record<TelegramSendFailureReason, string> = { /* … */ unknown: 'E_UNKNOWN' }
|
|
30
|
+
// after
|
|
31
|
+
const code: Record<TelegramSendFailureReason, string> = {
|
|
32
|
+
/* … */ 'bot-to-bot-disabled': 'E_BOT_TO_BOT', unknown: 'E_UNKNOWN',
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Search pattern: `TelegramSendFailureReason`, `TELEGRAM_SEND_FAILURE_REASONS`
|
|
37
|
+
and every `switch` over a send-failure reason.
|
|
38
|
+
2. Type-check: the exhaustive table without the new member fails to compile.
|
|
39
|
+
3. If the application wants one outbound budget per bot token, opt in at the
|
|
40
|
+
one transformer that already owns its Bot API calls:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { telegramOutboundBudget } from 'stitchkit/telegram'
|
|
44
|
+
|
|
45
|
+
const budget = telegramOutboundBudget({ token: env.BOT_TOKEN })
|
|
46
|
+
api.config.use((previous, method, payload, signal) =>
|
|
47
|
+
budget.run(method, payload, signal, () => previous(method, payload, signal)),
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Remove the old limiter rather than running it beside this one. See the
|
|
52
|
+
[Telegram guide](telegram.md#one-token-one-outbound-budget).
|
|
53
|
+
|
|
12
54
|
## Released migration: 0.113.0
|
|
13
55
|
|
|
14
56
|
**Who must act:** custom `AgentRuntimeStoreDriver` adapters and custom `AgentRuntimeStore` implementations of `seedConversationInput`. The built-in memory and SQLite stores already implement the contract.
|
|
@@ -814,84 +856,3 @@ signals to `createCli` / `createCliInvoker` and cooperative handlers observe `ct
|
|
|
814
856
|
`REQUEST_ABORTED` is nonretryable and exits 130. A disconnected caller does not prove that
|
|
815
857
|
an origin ignoring cancellation or an external provider stopped; keep resource leases until
|
|
816
858
|
that work actually completes.
|
|
817
|
-
|
|
818
|
-
## Released migration: 0.103.13
|
|
819
|
-
|
|
820
|
-
### Darwin native assets in JS distributions
|
|
821
|
-
|
|
822
|
-
**Who must act:** Bun builds that import `stitchkit/server`, `stitchkit/files` or
|
|
823
|
-
`stitchkit/process` (Linux-only server apps included) and use `--outfile`, copy only
|
|
824
|
-
`outputs[0]`, or package a single file; custom single-file/archive packagers and installers
|
|
825
|
-
delivering native process/files capabilities.
|
|
826
|
-
First affected publication: **0.103.13**, through **0.104.2**. It shipped as a patch; its
|
|
827
|
-
changed delivery graph requires this migration despite unchanged API signatures.
|
|
828
|
-
|
|
829
|
-
In those versions every Bun bundle that reaches the lock or process-identity code,
|
|
830
|
-
`stitchkit/server` included, carries both Darwin `.node` addons as extra outputs on every
|
|
831
|
-
operating system. `bun build --outfile` then fails with
|
|
832
|
-
`cannot write multiple output files without an output directory`, and `--outdir` writes the
|
|
833
|
-
addons next to a Linux artifact. Releases after 0.104.2 resolve the default loader at run time,
|
|
834
|
-
so these bundles are one JS file with no `.node` output again. The static loader exists only
|
|
835
|
-
in a build that uses the `stitchkit/files/packaging` plugin.
|
|
836
|
-
|
|
837
|
-
```sh
|
|
838
|
-
# Before: one JS file was assumed to be the entire distribution.
|
|
839
|
-
bun build src/native.ts --target=bun --outfile=dist/native.js
|
|
840
|
-
# After: retain every output, with its relative path.
|
|
841
|
-
bun build src/native.ts --target=bun --outdir=dist --entry-naming=native.js
|
|
842
|
-
```
|
|
843
|
-
|
|
844
|
-
For `Bun.build`, copy **all** `result.outputs`, not just the JS entry or `outputs[0]`.
|
|
845
|
-
A single-file JS distribution cannot embed a native `.node` addon automatically.
|
|
846
|
-
An archive may contain JS plus companion assets, materialized together before execution.
|
|
847
|
-
Custom output layout uses the evolving build-only packaging contract in the
|
|
848
|
-
[native IO guide](native-io.md#public-native-packaging).
|
|
849
|
-
|
|
850
|
-
Three deliveries have different rules: an ordinary npm install retains the package's
|
|
851
|
-
native assets; a JS distribution must ship its complete output graph; a Bun compiled
|
|
852
|
-
executable embeds the matching addon and may still use `--outfile`. In 0.103.13–0.104.2 a plain
|
|
853
|
-
`bun build` carries the addon without a plugin. Later releases load it from a bundle only when
|
|
854
|
-
the build uses `createNativePackaging` (`delivery: 'companion'` for JS, `'embedded'` for a
|
|
855
|
-
compiled executable). Without the plugin, Bun may still copy the addon's bytes into a compiled
|
|
856
|
-
executable, but the loader never uses them, so their presence proves nothing.
|
|
857
|
-
|
|
858
|
-
What an unpackaged bundle does depends on the release:
|
|
859
|
-
|
|
860
|
-
- **0.105.0–0.106.1:** the bundle carries the absolute path of the package on the build machine,
|
|
861
|
-
because the loader read `__dirname`. It looks for the addon there, so it works only on that
|
|
862
|
-
machine, and the path itself leaks into a distributed artifact. Rebuild with a later release.
|
|
863
|
-
- **0.107.0 and later:** no build path is in the artifact, and the first call that needs the
|
|
864
|
-
addon reports the Darwin backend `unavailable` with stage `packaging`.
|
|
865
|
-
|
|
866
|
-
Only macOS is affected, and only process identity (`observeProcessInstance`,
|
|
867
|
-
`probeProcessOwner`), exclusive locks and contained Agent file operations load the addon. The
|
|
868
|
-
managed file boundary, atomic writes and the chunk spool never do. The
|
|
869
|
-
[native IO guide](native-io.md#libraries-with-a-zod-only-runtime) is the canonical recipe and
|
|
870
|
-
shows how to check an artifact by its loader without running it on a Mac. Check the resulting
|
|
871
|
-
artifact offline outside the build tree and `node_modules`, including process identity,
|
|
872
|
-
live-owner lock refusal, dead-owner recovery and contained IO. A missing or corrupt addon
|
|
873
|
-
remains `unavailable`, never evidence that an owner died.
|
|
874
|
-
|
|
875
|
-
### CLI: repeated options are refused
|
|
876
|
-
|
|
877
|
-
**Who must act:** wrappers, aliases and scripts that append `--json`, `--profile X` or an
|
|
878
|
-
application global to arguments the user may already have given, and anything that passes the
|
|
879
|
-
same global twice.
|
|
880
|
-
First affected publication: **0.103.13**. It shipped as a patch; the change in grammar needs
|
|
881
|
-
this migration.
|
|
882
|
-
|
|
883
|
-
A repeated option that is not an array field is an argument error:
|
|
884
|
-
`--json --json` and `--profile a --profile b` fail with `--json was passed 2 times`. Before
|
|
885
|
-
0.103.13 the last value won, for framework globals, application globals and repeated
|
|
886
|
-
`kind: 'other'` fields alike. Array fields, and untyped passthrough fields that keep their
|
|
887
|
-
raw-list grammar, still repeat.
|
|
888
|
-
|
|
889
|
-
```sh
|
|
890
|
-
# Before: a wrapper appended its default and the last value won.
|
|
891
|
-
mytool deploy --profile staging "$@" --profile production
|
|
892
|
-
# After: build the argument list once, so each option appears once.
|
|
893
|
-
mytool deploy --profile "${PROFILE:-production}" "$@"
|
|
894
|
-
```
|
|
895
|
-
|
|
896
|
-
Dotted leaves such as `--filter.name` follow the same rule as their parent field. Declare a
|
|
897
|
-
field as an array when repetition is meant.
|