stitchkit 0.115.0 → 0.117.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -1
  3. package/dist/agent-runtime/run-acquisition.d.ts +9 -2
  4. package/dist/agent-runtime/run-acquisition.d.ts.map +1 -1
  5. package/dist/agent-runtime/run-execution.d.ts +3 -0
  6. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  7. package/dist/agent-runtime/run-failure.d.ts +4 -1
  8. package/dist/agent-runtime/run-failure.d.ts.map +1 -1
  9. package/dist/agent-runtime/run-store-retry.d.ts +8 -0
  10. package/dist/agent-runtime/run-store-retry.d.ts.map +1 -0
  11. package/dist/agent-runtime/run-terminal.d.ts.map +1 -1
  12. package/dist/agent-runtime/run-unacquired.d.ts +19 -0
  13. package/dist/agent-runtime/run-unacquired.d.ts.map +1 -0
  14. package/dist/agent-runtime/store-conformance.d.ts +8 -0
  15. package/dist/agent-runtime/store-conformance.d.ts.map +1 -1
  16. package/dist/agent-runtime/store-driver-contract.d.ts +5 -0
  17. package/dist/agent-runtime/store-driver-contract.d.ts.map +1 -1
  18. package/dist/agent-runtime/terminal-commit.d.ts +34 -4
  19. package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
  20. package/dist/entrypoints/agent-runtime/coding-tools.js +4 -4
  21. package/dist/entrypoints/agent-runtime/harness.js +4 -4
  22. package/dist/entrypoints/agent-runtime/sandbox.js +4 -4
  23. package/dist/entrypoints/agent-runtime/sqlite/bun.js +7 -7
  24. package/dist/entrypoints/agent-runtime/sqlite/node.js +7 -7
  25. package/dist/entrypoints/agent-runtime/testing.js +26 -8
  26. package/dist/entrypoints/agent-runtime.d.ts +1 -1
  27. package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
  28. package/dist/entrypoints/agent-runtime.js +17 -15
  29. package/dist/entrypoints/application/diagnostic-journal.js +1 -1
  30. package/dist/entrypoints/application/directory-inbox.js +3 -3
  31. package/dist/entrypoints/cli/publish.js +2 -2
  32. package/dist/entrypoints/cli.js +3 -3
  33. package/dist/entrypoints/files.js +2 -2
  34. package/dist/entrypoints/primitives.js +51 -154
  35. package/dist/entrypoints/server.js +6 -6
  36. package/dist/entrypoints/telegram/grammy.js +2 -2
  37. package/dist/entrypoints/telegram.d.ts +1 -1
  38. package/dist/entrypoints/telegram.d.ts.map +1 -1
  39. package/dist/entrypoints/telegram.js +203 -70
  40. package/dist/entrypoints/tools.js +3 -3
  41. package/dist/entrypoints/tracking/contract.d.ts +11 -0
  42. package/dist/entrypoints/tracking/contract.d.ts.map +1 -0
  43. package/dist/entrypoints/tracking/contract.js +162 -0
  44. package/dist/entrypoints/tracking/performance.d.ts +8 -0
  45. package/dist/entrypoints/tracking/performance.d.ts.map +1 -0
  46. package/dist/entrypoints/tracking/performance.js +181 -0
  47. package/dist/entrypoints/tracking/server.d.ts +1 -0
  48. package/dist/entrypoints/tracking/server.d.ts.map +1 -1
  49. package/dist/entrypoints/tracking/server.js +40 -12
  50. package/dist/entrypoints/tracking.d.ts +4 -3
  51. package/dist/entrypoints/tracking.d.ts.map +1 -1
  52. package/dist/entrypoints/tracking.js +43 -143
  53. package/dist/entrypoints/xlsx.d.ts +4 -0
  54. package/dist/entrypoints/xlsx.d.ts.map +1 -0
  55. package/dist/entrypoints/xlsx.js +406 -0
  56. package/dist/{index-crmswbrt.js → index-073nw9qr.js} +8 -8
  57. package/dist/index-1f40z584.js +7 -0
  58. package/dist/{index-nregbbsc.js → index-6f49krb5.js} +1 -1
  59. package/dist/{index-fdk4r4r6.js → index-75ns2ybj.js} +1 -1
  60. package/dist/{index-x0ddbj85.js → index-arjmb6de.js} +36 -1
  61. package/dist/{index-944x38nv.js → index-cycp3r3e.js} +125 -182
  62. package/dist/{index-dx5cm9pc.js → index-ghtpfz4z.js} +9 -9
  63. package/dist/{index-n3v695sb.js → index-hdjmdwsj.js} +277 -96
  64. package/dist/{index-7yx8j6ay.js → index-n5sv4z0w.js} +12 -3
  65. package/dist/{index-zqkz4af9.js → index-nqxrm176.js} +1 -1
  66. package/dist/{index-j8kf56wx.js → index-q968hy6y.js} +1 -1
  67. package/dist/index-qezhmp51.js +242 -0
  68. package/dist/index-vv1sch8a.js +94 -0
  69. package/dist/{index-3qqdqwxe.js → index-w7jq3fz9.js} +25 -5
  70. package/dist/{index-fggxgbrk.js → index-xx1j310k.js} +165 -102
  71. package/dist/internal/contained-files.d.ts +10 -0
  72. package/dist/internal/contained-files.d.ts.map +1 -1
  73. package/dist/internal/exclusive-lock.d.ts.map +1 -1
  74. package/dist/primitives/decimal.d.ts +10 -0
  75. package/dist/primitives/decimal.d.ts.map +1 -1
  76. package/dist/primitives/index.d.ts +3 -1
  77. package/dist/primitives/index.d.ts.map +1 -1
  78. package/dist/primitives/money.d.ts +20 -0
  79. package/dist/primitives/money.d.ts.map +1 -1
  80. package/dist/primitives/quantity.d.ts.map +1 -1
  81. package/dist/primitives/rounding.d.ts +40 -0
  82. package/dist/primitives/rounding.d.ts.map +1 -0
  83. package/dist/telegram/budget-transformer.d.ts +6 -3
  84. package/dist/telegram/budget-transformer.d.ts.map +1 -1
  85. package/dist/telegram/not-dispatched.d.ts +2 -2
  86. package/dist/telegram/not-dispatched.d.ts.map +1 -1
  87. package/dist/telegram/outbound-budget.d.ts +72 -20
  88. package/dist/telegram/outbound-budget.d.ts.map +1 -1
  89. package/dist/telegram/outbound-cost.d.ts +5 -0
  90. package/dist/telegram/outbound-cost.d.ts.map +1 -0
  91. package/dist/telegram/outbound-pauses.d.ts +17 -0
  92. package/dist/telegram/outbound-pauses.d.ts.map +1 -0
  93. package/dist/telegram/outbound-queue.d.ts +17 -0
  94. package/dist/telegram/outbound-queue.d.ts.map +1 -0
  95. package/dist/tools/cli/view.d.ts +7 -1
  96. package/dist/tools/cli/view.d.ts.map +1 -1
  97. package/dist/tracking/attribution.d.ts +1 -1
  98. package/dist/tracking/attribution.d.ts.map +1 -1
  99. package/dist/tracking/client-visit.d.ts +3 -1
  100. package/dist/tracking/client-visit.d.ts.map +1 -1
  101. package/dist/tracking/client.d.ts.map +1 -1
  102. package/dist/tracking/performance/collector.d.ts +61 -0
  103. package/dist/tracking/performance/collector.d.ts.map +1 -0
  104. package/dist/tracking/performance/schema.d.ts +45 -0
  105. package/dist/tracking/performance/schema.d.ts.map +1 -0
  106. package/dist/tracking/performance/summary.d.ts +12 -0
  107. package/dist/tracking/performance/summary.d.ts.map +1 -0
  108. package/dist/tracking/privacy.d.ts +5 -0
  109. package/dist/tracking/privacy.d.ts.map +1 -0
  110. package/dist/tracking/server/telemetry-health.d.ts +31 -0
  111. package/dist/tracking/server/telemetry-health.d.ts.map +1 -0
  112. package/dist/xlsx/cells.d.ts +17 -0
  113. package/dist/xlsx/cells.d.ts.map +1 -0
  114. package/dist/xlsx/render.d.ts +11 -0
  115. package/dist/xlsx/render.d.ts.map +1 -0
  116. package/dist/xlsx/schema.d.ts +119 -0
  117. package/dist/xlsx/schema.d.ts.map +1 -0
  118. package/dist/xlsx/zip.d.ts +13 -0
  119. package/dist/xlsx/zip.d.ts.map +1 -0
  120. package/llms/agent-runtime.2.txt +40 -16
  121. package/llms/agent-runtime.3.txt +17 -0
  122. package/llms/agent-runtime.txt +6 -3
  123. package/llms/cli.txt +3 -1
  124. package/llms/primitives.txt +21 -0
  125. package/llms/stitchkit.txt +9 -6
  126. package/llms/telegram.txt +39 -12
  127. package/llms/tracking-contract.txt +32 -0
  128. package/llms/tracking-performance.txt +21 -0
  129. package/llms/tracking-server.txt +1 -0
  130. package/llms/tracking.txt +75 -12
  131. package/llms/{upgrading-0.104.0-0.115.0.txt → upgrading-0.105.0-0.117.0.txt} +73 -124
  132. package/llms/{upgrading-0.44.0-0.61.0.txt → upgrading-0.44.0-0.62.0.txt} +120 -3
  133. package/llms/{upgrading-0.62.0-0.78.0.txt → upgrading-0.63.0-0.83.0.txt} +181 -120
  134. package/llms/{upgrading-0.79.0-0.103.13.txt → upgrading-0.84.0-0.104.0.txt} +124 -181
  135. package/llms/upgrading.txt +4 -4
  136. package/llms/xlsx.txt +107 -0
  137. package/llms-full.txt +390 -34
  138. package/llms.txt +15 -11
  139. package/package.json +27 -9
  140. package/dist/{index-z3d6smer.js → index-tdpmfks8.js} +5 -5
package/llms/tracking.txt CHANGED
@@ -33,7 +33,7 @@ Both entrypoints are **evolving**.
33
33
  ## Contract
34
34
 
35
35
  ```ts
36
- import { createTrackingContract } from 'stitchkit/tracking'
36
+ import { createTrackingContract } from 'stitchkit/tracking/contract'
37
37
 
38
38
  export const tracking = createTrackingContract({
39
39
  scope: 'public',
@@ -232,6 +232,76 @@ has an identity. A bot gets a lease that no store holds, so its events are
232
232
  `createPresenceRegistry` is who is here now, in this process: touch it from
233
233
  `track`, read it for a live feed. It is honestly empty after a restart.
234
234
 
235
+ ## Real-visitor speed
236
+
237
+ `stitchkit/tracking/performance` collects Web Vitals from the people who use the site. Install the
238
+ optional peer `web-vitals` and call it once on the page:
239
+
240
+ ```ts
241
+ import { createPerformanceCollector } from 'stitchkit/tracking/performance'
242
+
243
+ createPerformanceCollector({
244
+ buildId: BUILD_ID,
245
+ url: '/api/tracking/performance',
246
+ visitId: () => client.visitId(),
247
+ })
248
+ ```
249
+
250
+ The library is fetched as its own chunk after `load`, when the browser is idle, so the measurement costs
251
+ the page nothing it is measuring. Each load posts an `early` sample five seconds later and a `final` one
252
+ as the page goes, both with `sendBeacon` and the same `id`: keep the later one. The body is a string, so
253
+ the endpoint declares `safelistedBody: true`. A sample that could not leave waits in session storage for the
254
+ next page, up to 20 of them, and of one load only the newest.
255
+
256
+ A browser under automation sends nothing, and neither does an agent that admits to being Lighthouse,
257
+ headless Chrome, GTmetrix or a crawler. Lighthouse with its default settings imitates an ordinary browser, so a
258
+ local run with `webdriver` off cannot be told from a visitor.
259
+
260
+ One load is one document: a client-side navigation stays under the route the page opened on, and whatever
261
+ `web-vitals` reports after a back-forward restore is left out, because it would replace the load's numbers
262
+ with a restore's. A second call on a page returns the running collector.
263
+
264
+ A sample carries the route, the build, `mobile` or `desktop`, and the five metrics. **A metric that did not
265
+ arrive is `null`, never zero**: most visits have no INP at all, and a zero would pull every average down.
266
+
267
+ Parse a sample on your own endpoint and store it in your own table:
268
+
269
+ ```ts
270
+ import { redactTrackingText } from 'stitchkit/tracking'
271
+ import { PerformanceSampleSchema, summarizePerformanceMetric } from 'stitchkit/tracking/contract'
272
+
273
+ const sample = PerformanceSampleSchema.parse(await request.json())
274
+ await samples.upsert({ ...sample, route: redactTrackingText(sample.route) })
275
+
276
+ const lcp = summarizePerformanceMetric(rows.map((row) => row.vitals.lcpMs))
277
+ // { samples: 42, p75: 1840, p95: null } — a p95 needs 100 samples
278
+ ```
279
+
280
+ The report is yours: which routes matter and what is slow are the application's words.
281
+
282
+ ## Privacy
283
+
284
+ `redactTrackingText` masks passwords, sign-in credentials, API keys, bearer tokens and signed Telegram launch
285
+ data in a URL or free text, and leaves paths and campaign parameters byte for byte. Text past 16 KiB is cut
286
+ first, because the endpoint is open to anyone. `code` and `hash` count only beside the
287
+ `state` or `auth_date` that makes them credentials, so a promo code stays. `redactTrackingValue` applies the
288
+ same rule through a value of any shape. The client applies it to the landing path, the referrer and the
289
+ campaign tags before sending or storing them. Apply it again on your ingest: anyone can post to that endpoint without running the client.
290
+
291
+ ## Is tracking losing events?
292
+
293
+ `trackingTelemetryHealth(reports, baselines)` reads the outbox figures of a visit's bootstrap
294
+ (`browserStreamId` as `stream`, `outboxState`, `outboxQueued`, `outboxDropped`) and answers `ok`, `degraded` or
295
+ `unknown`. An application running without an outbox reports `unavailable` and reads as `degraded`. A browser repeats its lifetime `dropped` with
296
+ every visit, so pass what each stream reported before the window as `baselines`, and only the growth counts.
297
+
298
+ ## Bundle weight
299
+
300
+ `stitchkit/tracking` has no `zod` in its graph: `createTrackingClient` with `indexedDbOutboxStorage` is under
301
+ 8 KB gzip, a test holds that, and the build refuses a published file that reaches `zod`. The schemas live in
302
+ `stitchkit/tracking/contract` for the server and a shared contract. Importing that in code a browser loads
303
+ brings `zod` back, so keep a shared contract out of the storefront's client bundle.
304
+
235
305
  ## Not here
236
306
 
237
307
  GeoIP, event labels, funnels, reports, the analytics page, Socket.IO push into
@@ -246,8 +316,9 @@ looks different even though their mechanics are now one.
246
316
 
247
317
  Browser-safe visitor-tracking mechanics, with no event vocabulary of their own
248
318
  and no React. The React wrapper (a provider and a hook, ~40 lines) is the
249
- application's — it owns the router and the session. The server half is
250
- `stitchkit/tracking/server`. → ADR 0166 — [guide](../guide/tracking.md)
319
+ application's — it owns the router and the session. It imports no `zod`: the wire
320
+ shapes are `stitchkit/tracking/contract`, the server half is
321
+ `stitchkit/tracking/server`. → ADR 0166, ADR 0280 — [guide](../guide/tracking.md)
251
322
 
252
323
  | Export | Kind | Summary |
253
324
  |--------|------|---------|
@@ -256,15 +327,6 @@ application's — it owns the router and the session. The server half is
256
327
  | `TrackFn` / `EventsWithMetadata` / `EventsWithoutMetadata` | _type_ | `track(type)` / `track(type, metadata)` typed by the application's metadata map, and the two halves of that map |
257
328
  | `BuiltinTrackingEventTypes` / `CONVENTIONAL_TRACKING_EVENT_TYPES` | _type_ / const | the application's names for the events the client emits itself; the conventional `PAGE_VIEW` … `INTERACTION` set |
258
329
  | `browserTrackingHost` / `TrackingHost` / `TrackingPageContext` | function / _type_ | everything the client takes from a browser tab behind one interface — page context, visibility, scroll depth, events, intervals, clocks, storage — so a test or a non-tab host can supply its own |
259
- | `createTrackingContract` / `TrackingContractConfig` / `TrackingContractEndpoints` | function / _type_ | `bootstrap` (`POST /visit`) and `track` (`POST /events`, `safelistedBody`) from the application's event types, scope and optional extras |
260
- | `createTrackingSchemas` / `TrackingSchemas` / `TrackingSchemasConfig` | function / _type_ | every schema of one surface — `event`, `request`, `response`, `disposition`, `entry`, `bootstrap` — from `eventTypes`, optional `eventExtras`, `buildIdPattern`, `maxEventsPerBatch` |
261
- | `TrackingEventEnvelope` / `TrackingEventShape` | _type_ | `eventId`, `visitId`, `browserStreamId`, `browserSequence`, `type`, `page`, `metadata`, `clientTimestamp` |
262
- | `TrackEventsRequest` / `TrackEventsResponse` / `TrackEventsResponseSchema` | _type_ / schema | a batch (`buildId`, `events`, `utm`) and its answer (`accepted`, `dispositions`) |
263
- | `TrackingDisposition` / `TrackingDispositionSchema` / `TrackingDispositionStatus` / `TrackingDispositionStatusSchema` | _type_ / schema | `accepted` / `duplicate` / `identity-invalid` / `excluded-bot` — every one terminal |
264
- | `VisitEntryContext` / `VisitEntryContextShape` / `VisitBootstrapResponse` / `VisitBootstrapResponseSchema` | _type_ / schema | what a browser reports to open a visit (lineage, landing, referrer, UTM, display, screen, `buildId`, outbox health) and the lease it gets back |
265
- | `TrackingOutboxState` / `TrackingOutboxStateSchema` | _type_ / schema | `available` / `unavailable` |
266
- | `UtmData` / `UtmDataSchema` / `AttributionData` / `AttributionDataSchema` | _type_ / schema | campaign tags and a touch (UTM, referrer, landing page) |
267
- | `DEFAULT_BUILD_ID_PATTERN` | const | `dev` or a git SHA |
268
330
  | `createTrackingOutbox` / `TrackingOutbox` / `TrackingOutboxOptions` / `TrackingOutboxHealth` | function / _type_ | the tab-shared outbox: `streamId`, `reserveSequences`, `enqueue`, `acquireLease` / `releaseLease` (short, released on leave), `readBatch`, `acknowledge`, `health`; bounded by count and age |
269
331
  | `TrackingOutboxStorage` / `TrackingOutboxMeta` / `TrackingOutboxEvents` / `TrackingOutboxRecord` / `TrackingQueuedEvent` | _type_ | one atomic, all-or-nothing `transact` over a key/value store and an event store (`readonly` for pure reads) — the contract an adapter implements |
270
332
  | `indexedDbOutboxStorage` / `memoryOutboxStorage` | function | the IndexedDB adapter (one `readwrite` transaction per `transact`) and the in-memory one for tests and degraded mode |
@@ -276,6 +338,7 @@ application's — it owns the router and the session. The server half is
276
338
  | `createOncePerPage` / `OncePerPage` | function / _type_ | one fact per key per page within a window — the guard a twice-mounted component cannot keep itself |
277
339
  | `resolveAttribution` / `ResolveAttributionInput` / `ResolvedAttribution` / `AttributionStorage` / `ReferrerRule` | function / _type_ | first-touch once, current-touch on every new UTM; the referrer → source/medium map is the application's, there is no built-in list |
278
340
  | `parseUtmFromSearch` / `parseReferrer` | function | the two parsers `resolveAttribution` is built from |
341
+ | `redactTrackingText` / `redactTrackingValue` | functions | mask passwords, sign-in credentials and signed launch data in text or a value of any shape; the key stays, and navigation and campaign parameters are untouched |
279
342
  | `resolveTrackedClick` / `ResolveTrackedClickOptions` / `TrackedClick` / `TrackedClickAttributes` / `ClickTarget` | function / _type_ | `data-track` → click, `data-track-action` → interaction, a link to another origin → outbound; attribute names configurable; `hrefPolicy: 'full'` retains query/fragment (default `path-only` omits them), URL credentials excluded; `leavesPage` says which path to send on |
280
343
 
281
344
  ---
@@ -1,14 +1,84 @@
1
- # Upgrading 0.104.0 → 0.115.0 — stitchkit docs slice
1
+ # Upgrading 0.105.0 → 0.117.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.104.0 → 0.115.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.105.0 → 0.117.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.104.0 → 0.115.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.105.0 → 0.117.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.117.0
13
+
14
+ ### telegram-chat-pause
15
+
16
+ **Who must act:** code that relied on a 429 to one chat to stop the token's other chats. A bot that
17
+ only sends needs no change: its other chats stop waiting.
18
+
19
+ `run` pauses the `chat_id` of a refused send; the token pauses when `tokenPauseChats` chats are refused
20
+ inside `tokenPauseWindowMs`, or when the call has no chat
21
+ ([ADR 0278](../decisions/0278-a-telegram-429-holds-the-chat-that-got-it.md)). A second budget kept under a
22
+ derived key to isolate one group is no longer needed: remove it. Where one refusal must still stop everything,
23
+ say so:
24
+
25
+ ```ts
26
+ // before
27
+ await api.sendMessage(chatId, text)
28
+ // after
29
+ await api.sendMessage(chatId, text).catch((error) => {
30
+ budget.pauseAfter(error)
31
+ throw error
32
+ })
33
+ ```
34
+
35
+ The catch belongs at the `Api` call, where grammY has turned a returned refusal into a thrown one. A raw
36
+ `budget.run` around a client transformer's `send` gets that refusal as a value.
37
+
38
+ Search pattern: `budget.run`, and budgets created under a key derived from the token.
39
+
40
+ ### tracking-contract-entry
41
+
42
+ **Who must act:** code that imports `createTrackingContract` or a tracking schema or wire type from
43
+ `stitchkit/tracking`. Code that only uses the browser client needs no change.
44
+
45
+ The wire shapes moved to `stitchkit/tracking/contract`, so a page that sends events no longer carries `zod`
46
+ ([ADR 0280](../decisions/0280-real-visitor-speed-is-collected-after-the-page.md)):
47
+
48
+ ```ts
49
+ // before
50
+ import { createTrackingContract, createTrackingSchemas } from 'stitchkit/tracking'
51
+ // after
52
+ import { createTrackingContract, createTrackingSchemas } from 'stitchkit/tracking/contract'
53
+ ```
54
+
55
+ Search pattern: imports from `stitchkit/tracking` naming `createTrackingContract`, `createTrackingSchemas`,
56
+ `UtmData`, `AttributionData`, `TrackingEventEnvelope`, `TrackingDisposition`, `TrackEvents*`, `VisitEntryContext`,
57
+ `VisitBootstrapResponse`, `TrackingOutboxState` or `DEFAULT_BUILD_ID_PATTERN`.
58
+
59
+ ## Released migration: 0.116.0
60
+
61
+ ### send-only-telegram-pause
62
+
63
+ **Who must act:** code that relied on a 429 from `editMessage*`, `deleteMessage` or a read to stop a
64
+ token's sends. Code that only sends needs no change.
65
+
66
+ `run` pauses the token only on a method that spends the budget
67
+ ([ADR 0277](../decisions/0277-a-telegram-budget-serves-replies-first.md)). Where an edit must still stop
68
+ the sends, say so:
69
+
70
+ ```ts
71
+ // before
72
+ await budget.run('editMessageText', payload, signal, send)
73
+ // after
74
+ await budget.run('editMessageText', payload, signal, send).catch((error) => {
75
+ budget.pauseAfter(error)
76
+ throw error
77
+ })
78
+ ```
79
+
80
+ Search pattern: `budget.run` next to edit and read methods.
81
+
12
82
  ## Released migration: 0.115.0
13
83
 
14
84
  ### contained-cli-downloads
@@ -794,124 +864,3 @@ in the second position is a type error.
794
864
 
795
865
  4. Both classes accept the standard `cause`, which `error.cause` returns; omit the option to
796
866
  leave the property absent.
797
-
798
- ## Released migration: 0.104.0
799
-
800
- JS bundles carrying Darwin native capabilities also need the
801
- [delivery migration first affected in 0.103.13](#released-migration-010313).
802
- Consumers upgrading from 0.103.12 or earlier must apply it even if their TypeScript API is unchanged.
803
-
804
- ### CLI runtime-tool construction
805
-
806
- **Who must act:** callers writing a schema-dependent handler or MCP/AI `present` directly
807
- inside an annotated `CliConfig.runtimeTools` or `CliInvokerConfig.runtimeTools` object.
808
- Native `commands` and values returned by `defineRuntimeTool` / `createRuntimeToolFactory`
809
- remain valid. Execution, auth, validation and the invocation result envelope keep their behavior.
810
-
811
- Construct a CLI-only managed definition with the neutral schema-aware type before registration:
812
-
813
- ```ts
814
- import type { CliInvokerConfig, RuntimeToolExecutionWithOutput } from 'stitchkit/cli'
815
- import { z } from 'zod'
816
-
817
- const input = z.object({ text: z.string() })
818
- const output = z.object({ size: z.number() })
819
- // Before: handler receives the heterogeneous registry context.
820
- const config: CliInvokerConfig = {
821
- name: 'app',
822
- runtimeTools: [{
823
- name: 'measure', description: 'Measure text',
824
- identity: { serviceName: 'text', action: 'measure', method: 'POST' },
825
- transports: ['CLI'], input, output,
826
- handler: ({ input }) => ({ size: String(input.text).length }),
827
- }],
828
- }
829
- // After: handler input and output derive from the construction schemas.
830
- const measure = {
831
- name: 'measure', description: 'Measure text',
832
- identity: { serviceName: 'text', action: 'measure', method: 'POST' },
833
- transports: ['CLI'], input, output,
834
- handler: ({ input }) => ({ size: input.text.length }),
835
- } satisfies RuntimeToolExecutionWithOutput<typeof input, typeof output>
836
- const nextConfig: CliInvokerConfig = { name: 'app', runtimeTools: [measure] }
837
- ```
838
-
839
- For a shared MCP/AI tool, construct the definition with `defineRuntimeTool` from
840
- `stitchkit/tools`, including its typed SDK `present` callbacks, then pass the returned value
841
- to `runtimeTools`. Install the corresponding SDK peers for that integration. A CLI-only
842
- consumer needs `stitchkit`, `zod` and its ordinary Node type tooling.
843
-
844
- `RuntimeToolExecution` names the heterogeneous registration contract. Call a registered
845
- operation through `createCli` or `createCliInvoker`; its erased handler input is `never`
846
- because the managed runner owns schema validation before execution. For a managed void
847
- definition, use `RuntimeToolDefinitionWithoutOutput<typeof input>`.
848
-
849
- ### MCP construction, state stores and delivery
850
-
851
- **Who must act:** schema-dependent runtime tool literals written directly inside MCP mount
852
- configuration, MCP presenters returning malformed content or metadata, custom `StateStore`
853
- implementations, file-store consumers using retired lock
854
- options, broadcast consumers reading `halt` or retrying unknown send outcomes, and consumers
855
- that validate HTTP error envelopes with a closed schema. Applications collecting raw tool
856
- causes from `console.error` must configure an internal tool hook sink.
857
-
858
- MCP-only code can import the existing server factories from `stitchkit/tools/mcp` and
859
- construct its schema-aware tool before heterogeneous registration:
860
-
861
- ```ts
862
- import { buildMcpServer, type RuntimeMcpToolDefinitionWithOutput } from 'stitchkit/tools/mcp'
863
- import { z } from 'zod'
864
- const input = z.object({ value: z.number() })
865
- const output = z.object({ doubled: z.number() })
866
- // Before: runtimeTools: [{ input, output, handler, present }]
867
- const double = {
868
- name: 'double', description: 'Double a number',
869
- identity: { serviceName: 'numbers', action: 'double', method: 'POST' },
870
- input, output,
871
- handler: ({ input }) => ({ doubled: input.value * 2 }),
872
- present: { mcp: ({ doubled }) => ({ content: [{ type: 'text', text: String(doubled) }] }) },
873
- } satisfies RuntimeMcpToolDefinitionWithOutput<typeof input, typeof output>
874
- const server = buildMcpServer({ serverInfo: { name: 'numbers', version: '1' }, services: [], runtimeTools: [double] })
875
- ```
876
-
877
- Full MCP/AI integrations keep `defineRuntimeTool` from `stitchkit/tools`; those typed values
878
- remain accepted by the MCP leaf and CLI. The leaf requires the MCP SDK and Zod, with no AI peer.
879
-
880
- `RuntimeMcpPresentation` preserves the official SDK's required `content` and typed `_meta`
881
- fields. A presenter returning `{}` must return `{ content: [] }`; text blocks require string
882
- text, and metadata must be an object. `structuredContent` and `isError` remain framework-owned.
883
- This restores SDK checks that an indexed mapped type previously erased.
884
-
885
- Unexpected tool causes remain in `hooks.onToolError` and the `error` field of
886
- `hooks.afterToolCall`. Protocol stderr carries safe errors. MCP resolver failures use those
887
- same observers without executing the handler or its lifecycle again. HTTP's default
888
- unexpected-error logging retains its behavior.
889
-
890
- A custom state adapter's `update` must invoke `transition(current, context)` while it still
891
- owns the serialized transaction. `context.assertHeld()` checks the actual transaction or lock
892
- generation and refuses after completion, rollback or ownership loss. A no-op guard cannot
893
- satisfy this contract. Existing transition callbacks may keep one parameter. For the owning
894
- file adapter, replace `{ staleLockMs, retryMs, lockTimeoutMs }` with `{ lockTimeoutMs }`:
895
- the remaining budget controls acquisition waiting, never expiry of a live transaction.
896
- Unknown legacy PID locks and historical temporary files are preserved; recover them only
897
- after independently proving their owner is gone.
898
-
899
- Broadcast consumers change `report.halt.reason` checks to the closed `halt.kind` union:
900
- `retry-after`, `transient`, `permanent` or `ambiguous`. A custom MTProto adapter can classify
901
- an acknowledged flood refusal as `{ kind: 'retry-after', retryAfterMs: wait }`; the exact
902
- provider wait is respected. A wait over `maxRetryDelayMs` halts with the recipient pending.
903
- Unknown write outcome becomes `uncertain`; restarting the same broadcast does not send it
904
- again. A retry classification must certify that the attempt did not apply the external effect.
905
-
906
- HTTP errors gain optional boolean `error.retryable`; framed contract streams use the same
907
- field. Update closed consumer schemas before producers declaring it. `false` on 502 and
908
- `true` on 409 remain explicit recommendations through the remote tool path. An absent field
909
- retains prior status-derived defaults; the field does not change the HTTP client's retry policy.
910
- The existing seventh `ApiError` constructor argument remains `ErrorOptions`; retryability is
911
- the new eighth argument.
912
-
913
- Caller cancellation now reaches `implementRemote`'s existing HTTP call. Applications bind
914
- signals to `createCli` / `createCliInvoker` and cooperative handlers observe `ctx.signal`.
915
- `REQUEST_ABORTED` is nonretryable and exits 130. A disconnected caller does not prove that
916
- an origin ignoring cancellation or an external provider stopped; keep resource leases until
917
- that work actually completes.
@@ -1,14 +1,131 @@
1
- # Upgrading 0.44.0 → 0.61.0 — stitchkit docs slice
1
+ # Upgrading 0.44.0 → 0.62.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.44.0 → 0.61.0 (docs/guide/upgrading.md)
6
+ Contains: Guide: Upgrading, 0.44.0 → 0.62.0 (docs/guide/upgrading.md)
7
7
 
8
8
  ==============================================================================
9
- # Guide: Upgrading, 0.44.0 → 0.61.0 (docs/guide/upgrading.md)
9
+ # Guide: Upgrading, 0.44.0 → 0.62.0 (docs/guide/upgrading.md)
10
10
  ==============================================================================
11
11
 
12
+ ## Released migration: 0.62.0
13
+
14
+ Two groups of behaviour changes. Nothing moves an export — the surface is
15
+ strictly additive — and every item changes what a running system reports or
16
+ sends, which is what this heading is for. One of them changes a number you may
17
+ already be billing against.
18
+
19
+ ### A run reports what it spent
20
+
21
+ Three changes to what the runtime says about cost and tokens. Nothing moves an
22
+ export; all three change numbers a running system reports, and one of them
23
+ changes a number you may already be billing against.
24
+
25
+ #### Multi-step cost was under-reported and is now summed
26
+
27
+ No code change is needed to get the fix — but check any predicate that reads
28
+ `provenance`:
29
+
30
+ ```ts
31
+ // before — accepted a number that was one step's cost, not the run's
32
+ if (usage.cost?.provenance === 'provider-reported') bill(usage.cost.value)
33
+
34
+ // after — a sum stitchkit performed says so
35
+ if (usage.cost && usage.cost.provenance !== 'unavailable') bill(usage.cost.value)
36
+ ```
37
+
38
+ `'computed'` means stitchkit added up provider-reported parts. It is not a guess
39
+ — `'estimated'` is the word for that — but it is deliberately not
40
+ `'provider-reported'`, because that label is what a caller filters on when it
41
+ wants a figure it can bill against unchanged, and a sum is not one.
42
+
43
+ Token totals moved with it. The AI SDK's `totalUsage` is a sum *it* performed
44
+ over per-step provider figures — not a run total any provider handed over — so
45
+ labelling it `provider-reported` was the same overstatement. **A run total on a
46
+ terminal event is always `computed`.** If you want a figure with the provider's
47
+ own word on it, read `step-finished`: each step carries what that call reported.
48
+
49
+ #### `usage` is always present on a terminal event
50
+
51
+ ```ts
52
+ // before — absent when the run ended before the provider's `finish`
53
+ const spent = event.usage?.cost?.value ?? 0 // silently 0 for a real spend
54
+
55
+ // after — present, and it says what it does not know
56
+ event.usage?.cost?.provenance === 'unavailable' // we spent, and cannot say how much
57
+ ```
58
+
59
+ Keep the optional chaining: one `AgentRunEvent` shape covers `run-started`,
60
+ `step-finished` and `run-terminal`, so `usage` stays optional on the type. The
61
+ guarantee is about terminal events, and a schema shared with `run-started`
62
+ cannot express it.
63
+
64
+ **Do not read `unavailable` as zero.** A run aborted mid-stream has spent real
65
+ money that nobody has counted; a run that never reached the provider has not.
66
+ Both used to look the same and now do not.
67
+
68
+ #### A losing executor reports its own spend
69
+
70
+ An execution that loses the terminal compare-and-swap now emits an operator
71
+ `run-terminal` event, because it ran and it spent. If a sink treated those events
72
+ as "runs this process committed", that is no longer true — `AgentRuntimeResult.metrics`
73
+ is still `undefined` for a losing executor and remains the way to tell.
74
+
75
+ The *delivery* `terminal` event is unchanged and still fires only for the winner,
76
+ so nothing delivers a turn twice.
77
+
78
+ ### An interrupted answer stops passing as a finished one
79
+
80
+ Two behaviour changes, one shared cause: a run ended by a newer input used to
81
+ leave its half-written answer in the conversation with no sign that it was cut
82
+ off, and the next request to the provider carried it as an ordinary assistant
83
+ turn.
84
+
85
+ #### The projection marks an interrupted turn
86
+
87
+ Nothing to change to adopt the fix — the default is the fixed behaviour. What to
88
+ check is whether the marker is the *right* form for your surface, and the
89
+ question that decides it is not "was the run interrupted" but **"did anyone see
90
+ what it produced"**.
91
+
92
+ ```ts
93
+ // after — pick the form; the default is 'assistant-marked'
94
+ createAgentRuntime({ history: { interruptedAssistant: 'system-note' } })
95
+ ```
96
+
97
+ - **The user pressed stop and the text was on their screen** — keep
98
+ `'assistant-marked'`. The assistant turn is the truthful record of what the
99
+ human read, and the model should stay consistent with it.
100
+ - **The partial never reached anyone** — a surface that sends nothing until the
101
+ run is done — prefer `'system-note'`. An assistant turn in provider history is
102
+ a commitment the model stays consistent with; a system line is context.
103
+
104
+ If you pass `history.project` you own the projection outright and none of this
105
+ applies — but the same question does.
106
+
107
+ #### A run ended by a newer input can now say so
108
+
109
+ ```ts
110
+ // before
111
+ runs: { inputPolicy: 'interrupt' } // ends the run, keeps its partial answer
112
+
113
+ // after — for a surface where a follow-up message invalidates the answer
114
+ // in flight rather than merely stopping it
115
+ runs: { inputPolicy: 'supersede' } // ends the run, discards its partial answer
116
+ ```
117
+
118
+ `inputPolicy` also accepts `(input) => policy`, which is how one application
119
+ gives two conversation surfaces different rules without the runtime learning
120
+ which is which.
121
+
122
+ A superseded run terminates with `terminalReason: 'superseded'`, state
123
+ `'superseded'` and an assistant message of status `'superseded'`. **If you switch
124
+ exhaustively on any of those enums, add the arm** — that is the part of this
125
+ release that can break a build rather than a behaviour. The record itself is
126
+ kept: it is excluded from the projection, not deleted, so an operator can still
127
+ see what was thrown away.
128
+
12
129
  ## Released migration: 0.61.0
13
130
 
14
131
  Three behaviour changes between versions. None moves an export — the surface is