stitchkit 0.103.12 → 0.104.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 (225) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/darwin-native.cjs +12 -0
  3. package/darwin-native.d.cts +2 -0
  4. package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
  5. package/dist/application/directory-inbox-accept.d.ts +13 -0
  6. package/dist/application/directory-inbox-accept.d.ts.map +1 -0
  7. package/dist/application/directory-inbox-contract.d.ts +42 -5
  8. package/dist/application/directory-inbox-contract.d.ts.map +1 -1
  9. package/dist/application/directory-inbox-delivery.d.ts +14 -0
  10. package/dist/application/directory-inbox-delivery.d.ts.map +1 -0
  11. package/dist/application/directory-inbox-files.d.ts +28 -0
  12. package/dist/application/directory-inbox-files.d.ts.map +1 -0
  13. package/dist/application/directory-inbox-state.d.ts +14 -0
  14. package/dist/application/directory-inbox-state.d.ts.map +1 -0
  15. package/dist/application/directory-inbox.d.ts +2 -1
  16. package/dist/application/directory-inbox.d.ts.map +1 -1
  17. package/dist/application/file-state-store.d.ts +2 -8
  18. package/dist/application/file-state-store.d.ts.map +1 -1
  19. package/dist/application/state-store.d.ts +6 -1
  20. package/dist/application/state-store.d.ts.map +1 -1
  21. package/dist/browser/client-arguments.d.ts +4 -0
  22. package/dist/browser/client-arguments.d.ts.map +1 -0
  23. package/dist/browser/client-executors.d.ts.map +1 -1
  24. package/dist/browser/client.d.ts.map +1 -1
  25. package/dist/browser/contract-stream.d.ts.map +1 -1
  26. package/dist/browser/http.d.ts +2 -1
  27. package/dist/browser/http.d.ts.map +1 -1
  28. package/dist/browser/native-fetch.d.ts +8 -0
  29. package/dist/browser/native-fetch.d.ts.map +1 -0
  30. package/dist/contract/errors.d.ts +16 -12
  31. package/dist/contract/errors.d.ts.map +1 -1
  32. package/dist/contract/normalize.d.ts +5 -1
  33. package/dist/contract/normalize.d.ts.map +1 -1
  34. package/dist/contract/stream.d.ts +1 -0
  35. package/dist/contract/stream.d.ts.map +1 -1
  36. package/dist/entrypoints/agent-runtime/browser.js +3 -3
  37. package/dist/entrypoints/agent-runtime/coding-tools.js +8 -6
  38. package/dist/entrypoints/agent-runtime/harness-tools.js +8 -8
  39. package/dist/entrypoints/agent-runtime/harness.js +12 -11
  40. package/dist/entrypoints/agent-runtime/realtime.js +3 -3
  41. package/dist/entrypoints/agent-runtime/sandbox.js +8 -6
  42. package/dist/entrypoints/agent-runtime.js +13 -13
  43. package/dist/entrypoints/application/diagnostic-journal.js +3 -2
  44. package/dist/entrypoints/application/directory-inbox.js +424 -119
  45. package/dist/entrypoints/application/grammy.js +1 -1
  46. package/dist/entrypoints/application.d.ts +2 -2
  47. package/dist/entrypoints/application.d.ts.map +1 -1
  48. package/dist/entrypoints/application.js +7 -3
  49. package/dist/entrypoints/cli.d.ts +2 -0
  50. package/dist/entrypoints/cli.d.ts.map +1 -1
  51. package/dist/entrypoints/cli.js +757 -136
  52. package/dist/entrypoints/contract.js +3 -3
  53. package/dist/entrypoints/files.js +11 -107
  54. package/dist/entrypoints/index.js +7 -7
  55. package/dist/entrypoints/node.js +8 -8
  56. package/dist/entrypoints/observability.js +2 -2
  57. package/dist/entrypoints/process.js +2 -1
  58. package/dist/entrypoints/react.js +1 -1
  59. package/dist/entrypoints/remote.js +19 -6
  60. package/dist/entrypoints/server.d.ts +1 -1
  61. package/dist/entrypoints/server.d.ts.map +1 -1
  62. package/dist/entrypoints/server.js +20 -14
  63. package/dist/entrypoints/telegram.d.ts +1 -0
  64. package/dist/entrypoints/telegram.d.ts.map +1 -1
  65. package/dist/entrypoints/telegram.js +214 -98
  66. package/dist/entrypoints/testing.js +7 -7
  67. package/dist/entrypoints/tools/connections.d.ts +2 -2
  68. package/dist/entrypoints/tools/connections.d.ts.map +1 -1
  69. package/dist/entrypoints/tools/connections.js +427 -294
  70. package/dist/entrypoints/tools/invoker.js +6 -6
  71. package/dist/entrypoints/tools/mcp.d.ts +12 -0
  72. package/dist/entrypoints/tools/mcp.d.ts.map +1 -0
  73. package/dist/entrypoints/tools/mcp.js +45 -0
  74. package/dist/entrypoints/tools.d.ts +1 -0
  75. package/dist/entrypoints/tools.d.ts.map +1 -1
  76. package/dist/entrypoints/tools.js +82 -1147
  77. package/dist/entrypoints/tracking.js +1 -1
  78. package/dist/files/boundary.d.ts +2 -8
  79. package/dist/files/boundary.d.ts.map +1 -1
  80. package/dist/files/file-io.d.ts +0 -5
  81. package/dist/files/file-io.d.ts.map +1 -1
  82. package/dist/files/read.d.ts +2 -2
  83. package/dist/files/read.d.ts.map +1 -1
  84. package/dist/index-1a6jtnd9.js +88 -0
  85. package/dist/{index-fyphpknh.js → index-22rhg58f.js} +1 -1
  86. package/dist/index-269zqj8m.js +77 -0
  87. package/dist/{index-r34mvse1.js → index-27a2pj09.js} +1 -1
  88. package/dist/{index-r956rndn.js → index-2hyavjjj.js} +3 -3
  89. package/dist/{index-m2xqkv55.js → index-2vnh05w4.js} +83 -14
  90. package/dist/{index-pw99k9h5.js → index-43p08g7t.js} +1 -1
  91. package/dist/{index-xwq9j1sf.js → index-4e0ah203.js} +9 -7
  92. package/dist/{index-nybn2err.js → index-5csmzesf.js} +1 -1
  93. package/dist/{index-qapzv4p5.js → index-5z6jn897.js} +1 -1
  94. package/dist/{index-ahvsksw5.js → index-6d9xaxw1.js} +1 -1
  95. package/dist/{index-ahtnte39.js → index-6drst9sg.js} +46 -35
  96. package/dist/{index-x87j7csh.js → index-77nxk6ay.js} +2 -2
  97. package/dist/{index-dnn9z96q.js → index-a0zzh8ak.js} +3 -3
  98. package/dist/{index-kmpzj1cq.js → index-a3wma1hk.js} +1 -1
  99. package/dist/{index-j8aw2c0e.js → index-bakgedh8.js} +35 -7
  100. package/dist/index-bhb950qs.js +1145 -0
  101. package/dist/{index-tgzhq07n.js → index-cvda079a.js} +1 -1
  102. package/dist/{index-n2gre124.js → index-fa4wg312.js} +4 -4
  103. package/dist/{index-zp4jpkp0.js → index-fk18k56k.js} +2 -2
  104. package/dist/{index-afjvjyck.js → index-g2j7pnak.js} +2 -134
  105. package/dist/index-g95fk660.js +116 -0
  106. package/dist/{index-ej331hah.js → index-gxm9rc97.js} +1 -1
  107. package/dist/{index-r5c4fedv.js → index-h2fhjqy7.js} +2 -1
  108. package/dist/{index-sybedc8b.js → index-hbd32c6a.js} +23 -94
  109. package/dist/index-jd470k6q.js +109 -0
  110. package/dist/{index-b2bz0kdm.js → index-kb8styz3.js} +4 -3
  111. package/dist/{index-0c419yx7.js → index-kemm0ej5.js} +12 -9
  112. package/dist/{index-806d5phm.js → index-ptn0g0gj.js} +7 -7
  113. package/dist/{index-w575evhq.js → index-r4r70zmh.js} +1 -1
  114. package/dist/{index-12m2v2a9.js → index-s4evrmp5.js} +1 -1
  115. package/dist/{index-1khy8y7n.js → index-t1sr4aks.js} +3 -3
  116. package/dist/index-v5ee8ath.js +88 -0
  117. package/dist/{index-45tdvqa3.js → index-wh0ecvyk.js} +1 -1
  118. package/dist/{index-n53mswt3.js → index-x928baz2.js} +8 -1
  119. package/dist/index-yk4g8805.js +133 -0
  120. package/dist/{index-ygkka6zn.js → index-ywppsaby.js} +28 -5
  121. package/dist/{index-xvdd2rbb.js → index-za6j7nwy.js} +19 -5
  122. package/dist/internal/bounded-file-read.d.ts +41 -0
  123. package/dist/internal/bounded-file-read.d.ts.map +1 -0
  124. package/dist/internal/darwin-binding-error.d.ts +24 -0
  125. package/dist/internal/darwin-binding-error.d.ts.map +1 -0
  126. package/dist/internal/darwin-binding.d.ts.map +1 -1
  127. package/dist/internal/exclusive-lock.d.ts +2 -0
  128. package/dist/internal/exclusive-lock.d.ts.map +1 -1
  129. package/dist/internal/with-exclusive-lock.d.ts +4 -2
  130. package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
  131. package/dist/telegram/broadcast-delivery.d.ts +23 -0
  132. package/dist/telegram/broadcast-delivery.d.ts.map +1 -0
  133. package/dist/telegram/broadcast-failure.d.ts +22 -0
  134. package/dist/telegram/broadcast-failure.d.ts.map +1 -0
  135. package/dist/telegram/broadcast-state.d.ts +6 -3
  136. package/dist/telegram/broadcast-state.d.ts.map +1 -1
  137. package/dist/telegram/broadcast.d.ts +10 -5
  138. package/dist/telegram/broadcast.d.ts.map +1 -1
  139. package/dist/tools/cli/args-fields.d.ts +2 -0
  140. package/dist/tools/cli/args-fields.d.ts.map +1 -1
  141. package/dist/tools/cli/args.d.ts.map +1 -1
  142. package/dist/tools/cli/format.d.ts.map +1 -1
  143. package/dist/tools/cli/invoke.d.ts +2 -2
  144. package/dist/tools/cli/invoke.d.ts.map +1 -1
  145. package/dist/tools/cli/publication-assets.d.ts +10 -0
  146. package/dist/tools/cli/publication-assets.d.ts.map +1 -0
  147. package/dist/tools/cli/publication-contract.d.ts +75 -0
  148. package/dist/tools/cli/publication-contract.d.ts.map +1 -0
  149. package/dist/tools/cli/publication-control.d.ts +4 -0
  150. package/dist/tools/cli/publication-control.d.ts.map +1 -0
  151. package/dist/tools/cli/publication-storage.d.ts +32 -0
  152. package/dist/tools/cli/publication-storage.d.ts.map +1 -0
  153. package/dist/tools/cli/publication.d.ts +35 -0
  154. package/dist/tools/cli/publication.d.ts.map +1 -0
  155. package/dist/tools/cli/update.d.ts.map +1 -1
  156. package/dist/tools/connections/define.d.ts.map +1 -1
  157. package/dist/tools/connections/error-projection.d.ts +4 -0
  158. package/dist/tools/connections/error-projection.d.ts.map +1 -0
  159. package/dist/tools/connections/errors.d.ts +24 -8
  160. package/dist/tools/connections/errors.d.ts.map +1 -1
  161. package/dist/tools/connections/limits.d.ts +11 -31
  162. package/dist/tools/connections/limits.d.ts.map +1 -1
  163. package/dist/tools/connections/mcp-client.d.ts +6 -4
  164. package/dist/tools/connections/mcp-client.d.ts.map +1 -1
  165. package/dist/tools/connections/mcp.d.ts.map +1 -1
  166. package/dist/tools/connections/openapi.d.ts.map +1 -1
  167. package/dist/tools/connections/operation-limits.d.ts +25 -0
  168. package/dist/tools/connections/operation-limits.d.ts.map +1 -0
  169. package/dist/tools/connections/sse-frames.d.ts +11 -2
  170. package/dist/tools/connections/sse-frames.d.ts.map +1 -1
  171. package/dist/tools/connections/sse-session.d.ts +15 -2
  172. package/dist/tools/connections/sse-session.d.ts.map +1 -1
  173. package/dist/tools/connections/types.d.ts +19 -1
  174. package/dist/tools/connections/types.d.ts.map +1 -1
  175. package/dist/tools/execute-result.d.ts.map +1 -1
  176. package/dist/tools/execute.d.ts.map +1 -1
  177. package/dist/tools/internal/observe-hook.d.ts +3 -0
  178. package/dist/tools/internal/observe-hook.d.ts.map +1 -0
  179. package/dist/tools/internal/surface-projector.d.ts +2 -1
  180. package/dist/tools/internal/surface-projector.d.ts.map +1 -1
  181. package/dist/tools/mcp/mount.d.ts +2 -2
  182. package/dist/tools/mcp/mount.d.ts.map +1 -1
  183. package/dist/tools/mcp/prepare.d.ts +4 -5
  184. package/dist/tools/mcp/prepare.d.ts.map +1 -1
  185. package/dist/tools/mcp/register.d.ts +4 -1
  186. package/dist/tools/mcp/register.d.ts.map +1 -1
  187. package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
  188. package/dist/tools/remote.d.ts.map +1 -1
  189. package/dist/tools/runtime-tool-execution.d.ts +65 -0
  190. package/dist/tools/runtime-tool-execution.d.ts.map +1 -0
  191. package/dist/tools/runtime-tool-mcp.d.ts +27 -0
  192. package/dist/tools/runtime-tool-mcp.d.ts.map +1 -0
  193. package/dist/tools/runtime-tool.d.ts +9 -60
  194. package/dist/tools/runtime-tool.d.ts.map +1 -1
  195. package/dist/tools/surface.d.ts +12 -14
  196. package/dist/tools/surface.d.ts.map +1 -1
  197. package/llms/application.2.txt +221 -44
  198. package/llms/application.3.txt +3 -1
  199. package/llms/application.txt +1 -1
  200. package/llms/cli.2.txt +144 -1
  201. package/llms/cli.txt +133 -104
  202. package/llms/contract.txt +1 -1
  203. package/llms/process.txt +34 -5
  204. package/llms/server.4.txt +4 -1
  205. package/llms/server.5.txt +1 -0
  206. package/llms/stitchkit.2.txt +14 -0
  207. package/llms/stitchkit.txt +5 -4
  208. package/llms/telegram.txt +64 -2
  209. package/llms/tools-connections.txt +3 -0
  210. package/llms/tools-mcp.txt +29 -0
  211. package/llms/tools.2.txt +59 -0
  212. package/llms/tools.3.txt +1 -0
  213. package/llms/tools.txt +11 -52
  214. package/llms/upgrading-0.37.0-0.44.0.txt +444 -0
  215. package/llms/{upgrading-0.44.0-0.60.0.txt → upgrading-0.44.0-0.61.0.txt} +140 -271
  216. package/llms/{upgrading-0.61.0-0.74.0.txt → upgrading-0.62.0-0.79.0.txt} +171 -140
  217. package/llms/{upgrading-0.75.0-0.103.0.txt → upgrading-0.80.0-0.104.0.txt} +120 -171
  218. package/llms/upgrading.txt +4 -4
  219. package/llms-full.txt +677 -64
  220. package/llms.txt +13 -12
  221. package/package.json +10 -1
  222. package/dist/index-d4pa2bgs.js +0 -42
  223. package/dist/index-wykebpn9.js +0 -221
  224. package/llms/upgrading-0.37.0.txt +0 -176
  225. package/dist/{index-69nvze1q.js → index-pptyv13k.js} +3 -3
package/llms-full.txt CHANGED
@@ -54,10 +54,11 @@ own, recorded as an ADR.
54
54
  | `stitchkit/server` | server (Bun) | stable | `createServer`, `implement`, hooks, auth, Socket.IO server, server primitives |
55
55
  | `stitchkit/node` | server (Node ≥ 22) | stable | `serveNode` + the runtime-agnostic core — the Node mirror of `/server` |
56
56
  | `stitchkit/tools` | server | stable | `createMcpHandler`, `mountMcp`, `mountAgent`, the OAuth provider, native tools |
57
+ | `stitchkit/tools/mcp` | server (Bun or Node) | evolving | the canonical MCP HTTP/stdio server, schemas and typed MCP presenters; only Zod and the MCP server SDK peers |
57
58
  | `stitchkit/tools/contract` | browser + server | evolving | the shapes a tool surface speaks — async-operation contract, snapshot and cancel schemas, view-file input/output — without the runtime that serves them |
58
59
  | `stitchkit/tools/invoker` | server | stable | peer-free `createToolInvoker` over the canonical contract tool runner |
59
60
  | `stitchkit/tools/connections` | server (Bun or Node) | evolving | consume external MCP servers and a bounded OpenAPI subset — typed reauthorization, a per-request host fence and per-call credentials |
60
- | `stitchkit/cli` | server | stable | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
61
+ | `stitchkit/cli` | server | stable | `createCli` / `createCliInvoker` — runtime and strict NodeNext declarations without MCP SDK / `ai` |
61
62
  | `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
62
63
  | `stitchkit/process` | server (Bun or Node, POSIX) | evolving | finite native commands, byte sinks, bounded capture and process-group cancellation |
63
64
  | `stitchkit/files` | server (Bun or Node) | stable | peer-free managed local-file boundary |
@@ -71,12 +72,12 @@ own, recorded as an ADR.
71
72
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
72
73
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
73
74
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
74
- | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 48 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
75
+ | `stitchkit/observability` | server | stable<br>_redefined in 2 of the 49 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
75
76
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the managed-resource conformance kit |
76
77
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
77
78
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
78
79
  | `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) |
79
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 22 of the 48 minors since 0.56.2, most recently 0.97.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
80
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 22 of the 49 minors since 0.56.2, most recently 0.97.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
80
81
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
81
82
  | `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 |
82
83
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -89,7 +90,7 @@ own, recorded as an ADR.
89
90
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
90
91
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
91
92
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
92
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 11 of the 48 minors since 0.56.2, most recently 0.103.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
93
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 12 of the 49 minors since 0.56.2, most recently 0.104.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
93
94
  | `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
94
95
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
95
96
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -3131,6 +3132,13 @@ URLs.
3131
3132
 
3132
3133
  ## `ApiError`
3133
3134
 
3135
+ On native Bun, both client paths disable Bun's separate socket idle timer while preserving
3136
+ the framework deadline and caller signal. A long declared deadline therefore remains the
3137
+ request policy. Explicit or patched fetch implementations, including Next, keep their own
3138
+ transport behavior; Node and browsers receive no Bun-specific fetch options. Raw responses
3139
+ and contract streams retain their existing header timeout and caller-owned body lifetime.
3140
+ See [Bun's idle timeout contract](https://bun.sh/reference/globals/BunFetchRequestInit/timeout).
3141
+
3134
3142
  A non-2xx response is thrown as an `ApiError`:
3135
3143
 
3136
3144
  ```ts
@@ -3145,6 +3153,7 @@ try {
3145
3153
  err.message // 'Note not found'
3146
3154
  err.details // structured details, if any
3147
3155
  err.hint // optional hint
3156
+ err.retryable // optional explicit recommendation, true or false
3148
3157
  err.traceId // x-request-id — correlate this failure with backend logs
3149
3158
  err.cause // concrete injected transport failure, when delivery failed
3150
3159
  }
@@ -3158,6 +3167,12 @@ browser code can read it through Stitchkit's default CORS expose list; a custom
3158
3167
 
3159
3168
  The error model is shared with the server — see [Auth & errors](./auth-and-errors.md).
3160
3169
 
3170
+ `retryable` preserves an explicit boolean in the server error envelope; an absent declaration
3171
+ remains absent. This metadata does not change the HTTP transport retry policy. The existing
3172
+ seventh constructor argument remains `ErrorOptions`, including `cause`; the optional
3173
+ retryability argument is eighth. Closed error/frame schemas must accept the new optional
3174
+ field before their producer declares it.
3175
+
3161
3176
  ## Auth events
3162
3177
 
3163
3178
  The HTTP client emits events your app can react to globally:
@@ -3611,13 +3626,20 @@ get the view's defaults and projected answer, which is what `collectTools` and
3611
3626
 
3612
3627
  ## MCP — `createMcpHandler`
3613
3628
 
3629
+ An MCP-only server imports `stitchkit/tools/mcp` and installs
3630
+ `stitchkit`, `zod` and `@modelcontextprotocol/server`. The leaf's runtime and strict
3631
+ NodeNext declarations require no `ai`. It exports the existing HTTP/stdio server,
3632
+ schema policy, catalog and resource helpers; their implementation and security are shared
3633
+ with the full `stitchkit/tools` barrel. Use that full barrel when the application also
3634
+ needs its AI SDK features.
3635
+
3614
3636
  `createMcpHandler` builds a complete stateless Streamable-HTTP MCP endpoint on
3615
3637
  the official TypeScript SDK v2 server package. It owns server creation,
3616
3638
  protocol-era negotiation and shutdown; application code does not import the SDK
3617
3639
  to mount a normal endpoint.
3618
3640
 
3619
3641
  ```ts
3620
- import { createMcpHandler, createMcpHttpRoute } from 'stitchkit/tools'
3642
+ import { createMcpHandler, createMcpHttpRoute } from 'stitchkit/tools/mcp'
3621
3643
  import { createServer } from 'stitchkit/server'
3622
3644
 
3623
3645
  const mcp = createMcpHandler({
@@ -3751,7 +3773,7 @@ Every advertised tool and every tool result therefore carries the fingerprint of
3751
3773
  the catalog it came from:
3752
3774
 
3753
3775
  ```ts
3754
- import { readMcpCatalogStamp } from 'stitchkit/tools';
3776
+ import { readMcpCatalogStamp } from 'stitchkit/tools/mcp';
3755
3777
 
3756
3778
  const listed = await client.listTools();
3757
3779
  let known = readMcpCatalogStamp(listed.tools[0]?._meta); // { digest, tools }
@@ -4256,7 +4278,7 @@ If you already run an `McpServer` from the SDK, `mountMcp` adds contract tools
4256
4278
  to it instead of owning the lifecycle:
4257
4279
 
4258
4280
  ```ts
4259
- import { mountMcp } from 'stitchkit/tools'
4281
+ import { mountMcp } from 'stitchkit/tools/mcp'
4260
4282
 
4261
4283
  mountMcp(mcpServer, [usersService], { context: { source: 'mcp' } })
4262
4284
  ```
@@ -4272,7 +4294,7 @@ client (Claude Desktop, Claude Code, Cursor, Codex), on the user's machine, so
4272
4294
  it can reach the local filesystem.
4273
4295
 
4274
4296
  ```ts
4275
- import { bindStdioProcessSignals, createStdioMcpServer } from 'stitchkit/tools'
4297
+ import { bindStdioProcessSignals, createStdioMcpServer } from 'stitchkit/tools/mcp'
4276
4298
 
4277
4299
  const stdio = await createStdioMcpServer({
4278
4300
  serverInfo: { name: 'my-app', version: '1.0.0' },
@@ -4427,7 +4449,7 @@ through the contract's typed client:
4427
4449
  ```ts
4428
4450
  import { createHttpClient } from 'stitchkit'
4429
4451
  import { implementRemote } from 'stitchkit/remote'
4430
- import { createStdioMcpServer } from 'stitchkit/tools'
4452
+ import { createStdioMcpServer } from 'stitchkit/tools/mcp'
4431
4453
 
4432
4454
  const http = createHttpClient({
4433
4455
  baseUrl: 'https://api.example.com',
@@ -4453,6 +4475,17 @@ no duplicated business logic.
4453
4475
  `transformArgs` hook that rewrites a call's arguments before they are forwarded
4454
4476
  — e.g. to upload a local file referenced in the args and swap in its URL.
4455
4477
 
4478
+ The call's signal reaches the existing typed HTTP client's request options, for endpoints
4479
+ with or without arguments. An already cancelled call, including cancellation while an async
4480
+ argument transform runs, dispatches no request. Bind your process signal to `createCli` or
4481
+ `createCliInvoker`; `REQUEST_ABORTED` is a safe nonretryable failure with CLI exit130.
4482
+ Cooperative origins observe `ctx.signal` and release resources when their work ends. An
4483
+ origin or provider ignoring cancellation may continue after the local wait stops.
4484
+
4485
+ An explicit `AppError.retryable` boolean also survives this remote hop. `false` on502 and
4486
+ `true` on409 remain the application's recommendation through HTTP, CLI, MCP and AI tools.
4487
+ Absent metadata keeps the previous defaults and does not alter HTTP transport retries.
4488
+
4456
4489
  ## Structured output
4457
4490
 
4458
4491
  When a contract endpoint or runtime tool declares `output`, its MCP tool
@@ -6809,7 +6842,7 @@ description: Compose process-local resources, schedules, readiness, drain and op
6809
6842
  type: architecture
6810
6843
  status: active
6811
6844
  created: 2026-08-23
6812
- updated: 2026-09-30 17:41 +07:00
6845
+ updated: 2026-10-04 13:28 +07:00
6813
6846
  ---
6814
6847
 
6815
6848
  # Managed application kernel
@@ -7792,49 +7825,101 @@ deadline holds `stop()` — and so the resource's `force()` — for that one cal
7792
7825
  so give the transport a timeout. Superseding a key that is being sent right now
7793
7826
  does not recall it: the send completes, and only its receipt is not written.
7794
7827
 
7795
- `createDirectoryInbox` is the receiving side for another program on the same
7796
- host: it drops entries — a release transition, say — into a directory, and
7797
- the application takes each one at least once. It is a managed resource of its
7798
- own (`createDirectoryInbox({ id, directory, schema, handle })`, imported from
7799
- `stitchkit/application/directory-inbox` because it touches files; its types
7800
- and schemas are in `stitchkit/application`) and publishes a handle with
7801
- `flush()` and `state()`. The producer writes each entry
7802
- atomically as `<name>.json` — a temporary name, then a rename; names that start
7803
- with a dot are never read. Entries are delivered in name order, and only after
7804
- `activate`: none reaches the application before it is ready. `stopAdmission`
7805
- stops taking new ones, `drain` waits for the one in flight, and `force` aborts
7806
- the `signal` its handler was given.
7807
-
7808
- Taken and done are separate durable records. Before `handle` runs, the entry
7809
- is claimed with a lease (`leaseMs`, default five minutes); after it returns,
7810
- a receipt replaces the claim and only then is the file removed. A process that
7811
- died mid-delivery leaves a claim, and the entry is taken again once the lease
7812
- runs out — at least once, not exactly once, so a handler that sends somewhere
7813
- should carry the entry's `key` as its idempotency key. A process that died
7814
- after the receipt leaves only the file, which the next pass removes without
7815
- delivering it again. A handler that throws is retried after a backoff from one
7816
- second doubling to five minutes; an entry that fails `schema`, is over
7817
- `maxEntryBytes` or was taken `maxAttempts` times (default 20 — the count
7818
- survives restarts, so an entry whose handling kills the process is not taken
7819
- forever) is moved to `rejected/` with its reason and reported to `onRejected`,
7820
- and the entries after it keep flowing. The state lives in
7821
- `<directory>/.inbox-state.json` under `createFileStateStore`'s inter-process
7822
- lock unless a `store` is supplied.
7823
-
7824
- These primitives depend on the structural `StateStore`. On a server,
7825
- `createFileStateStore` supplies the shared Zod-validated JSON adapter with an
7826
- inter-process lock, unique temporary file, fsync and atomic rename. The lock
7827
- is a file with a heartbeat: the holder refreshes its mtime every third of
7828
- `staleLockMs` (default 3 s), a contender waits up to `lockTimeoutMs` (default
7829
- 10 s), and the stale bound must sit inside the timeout — otherwise a crashed
7830
- holder blocks every update until the lock ages out, which the constructor
7831
- refuses. A lock whose heartbeat is stale is reclaimed once its recorded pid is
7832
- gone; a live or unverifiable pid (a reused number, another user's process)
7833
- keeps it for ten stale bounds — and never fewer than thirty missed heartbeats,
7834
- so a scheduler stall on a loaded host cannot pull a live lock from under its
7835
- holder — after which the heartbeat wins and the lock is abandoned. Temporary files a crashed writer left beside the state are swept on
7836
- the store's first update. Ledger corruption may be declared reconstructable;
7837
- an outbox must fail closed rather than silently discard pending delivery.
7828
+ `createDirectoryInbox` receives entries from another program on the same host,
7829
+ or directly from a transport adapter. Import the resource from
7830
+ `stitchkit/application/directory-inbox`; its contracts live in
7831
+ `stitchkit/application`. Its published handle has `accept({ source, key, entry })`,
7832
+ `flush()` and `state()`:
7833
+
7834
+ ```ts
7835
+ const inbox = createDirectoryInbox({
7836
+ id: 'updates',
7837
+ directory: env.INBOX_DIRECTORY,
7838
+ schema: UpdateSchema,
7839
+ handle: ({ identity, entry, signal }) => processUpdate(identity, entry, signal),
7840
+ })
7841
+ const ingress = defineManagedResource({
7842
+ id: 'ingress',
7843
+ dependsOn: [inbox],
7844
+ start(context) {
7845
+ const intake = context.use(inbox)
7846
+ return { value: (key: string, entry: Update) =>
7847
+ intake.accept({ source: 'client-updates', key, entry }) }
7848
+ },
7849
+ })
7850
+ ```
7851
+
7852
+ `accept` is available after the resource starts, including before activation.
7853
+ It validates the entry and full producer identity, writes a lossless JSON envelope,
7854
+ syncs its file and parent directory, then returns
7855
+ `{ status: 'accepted' | 'duplicate', filename }`. Automatic delivery starts at
7856
+ `activate`; an explicit `flush()` is available before activation. A producer
7857
+ should acknowledge its provider only after acceptance resolves. Failure after
7858
+ publication can mean the entry was accepted but its acknowledgement was lost:
7859
+ retry the same identity, and `duplicate` keeps the original payload.
7860
+
7861
+ The identity is the pair `source` (1–128 characters) and `key` (1–1024 characters).
7862
+ A hash of the pair names the file; the full pair is retained inside its envelope
7863
+ and checked against existing files and records. Two sources may use the same key.
7864
+ The handler's legacy `key` remains the filename; programmatic entries additionally
7865
+ carry `identity`. No Telegram numeric update id or MTProto event shape is invented.
7866
+
7867
+ The default `maxPendingEntries: 1000` refuses new programmatic entries when the
7868
+ pending directory is full, without publishing another file; duplicate acceptance
7869
+ still succeeds. `maxEntryBytes` defaults to 1 MiB and counts the complete UTF-8
7870
+ envelope. A completed identity is deduplicated while its receipt is retained
7871
+ (`retain`, default 1000); a rejected envelope also deduplicates while its file
7872
+ remains in `rejected/`. Choose the retention and rejected-file cleanup policy to
7873
+ match the provider's replay window. Provider cursors, identity extraction and
7874
+ external-effect idempotency remain application responsibilities.
7875
+
7876
+ The original file-producer path also remains: atomically publish
7877
+ `<name>.json`, using a temporary name followed by a rename. Names starting with
7878
+ a dot are ignored; `intake-<64 hex characters>.json` is reserved for programmatic
7879
+ envelopes. Entries are delivered in filename order. The reader refuses unsafe
7880
+ leaf symlinks, multiple links and files changed during reading.
7881
+ `stopAdmission` stops new acceptance and delivery, `drain` waits for accepted
7882
+ storage operations and the handler in flight, and `force` aborts its handler
7883
+ signal. A handler ignoring that signal may still finish an external effect;
7884
+ the inbox leaves its entry unsettled rather than claiming it was cancelled.
7885
+
7886
+ Taken and done are separate durable records. Before `handle`, the entry is
7887
+ claimed with a unique expiring lease (`leaseMs`, default five minutes). A
7888
+ long handler renews that lease; an expired or replaced lease cannot complete,
7889
+ reject, retry or remove the successor's file. Success first commits a receipt,
7890
+ then removes only that file generation under a new protected transaction.
7891
+ A crash after receipt commit is recovered by cleanup without redelivery. A crash
7892
+ before receipt commit may redeliver: the guarantee is at least once, so use the
7893
+ full producer identity as the external idempotency key.
7894
+
7895
+ A throwing handler retries after a backoff from one second to five minutes.
7896
+ Schema failures, oversize files and `maxAttempts` exhaustion (default 20) are
7897
+ recorded before their files move to `rejected/`, with `onRejected` reporting the
7898
+ reason. The state defaults to `<directory>/.inbox-state.json`, or uses the supplied
7899
+ `store`. Manual file producers are responsible for atomic publication and capacity.
7900
+
7901
+ These primitives share the structural `StateStore`. `update` owns one atomic
7902
+ read/modify/write transaction and passes a `StateStoreUpdateContext` as the
7903
+ transition's second argument. Its `await context.assertHeld()` checks that the
7904
+ transaction is still active and its exclusive generation is still owned. A custom
7905
+ store must implement both real serialization/atomic commit and this guard;
7906
+ a no-op guard does not satisfy the contract. Ordinary one-argument transition
7907
+ callbacks remain valid.
7908
+
7909
+ On a server, `createFileStateStore` uses the same process-instance lock as
7910
+ `withExclusiveLock`, unique staging files and atomic directory-durable publication.
7911
+ `lockTimeoutMs` (default 10 s, zero means one attempt) bounds acquisition only.
7912
+ A live or unattributable owner is never replaced by age, including after an event
7913
+ loop stall or `SIGSTOP`; only a provably dead local process instance is reclaimed.
7914
+ Legacy readable pid/token records without a host are unknown owners and need
7915
+ operator recovery after the old writer is verified stopped.
7916
+
7917
+ `staleLockMs` and `retryMs` are removed and are refused at runtime. The current
7918
+ write removes its own staging file; unknown historical temporary files are
7919
+ preserved, without an age sweep. Directory durability requires a filesystem
7920
+ supporting directory sync (Linux and macOS); Windows is refused explicitly.
7921
+ Ledger corruption may be declared reconstructable; an outbox must fail closed
7922
+ rather than discard pending delivery.
7838
7923
 
7839
7924
  ## Recovering change subscriptions
7840
7925
 
@@ -8045,7 +8130,7 @@ description: Native IO для Bun и Node, с явно выбранной durabi
8045
8130
  type: guide
8046
8131
  status: active
8047
8132
  created: 2026-10-01 20:44 +07:00
8048
- updated: 2026-10-03 13:36 +07:00
8133
+ updated: 2026-10-04 09:40 +07:00
8049
8134
  participants:
8050
8135
  - role: authored
8051
8136
  harness: Codex
@@ -8059,6 +8144,10 @@ participants:
8059
8144
  harness: Codex
8060
8145
  model: GPT-6
8061
8146
  at: 2026-10-03 13:36 +07:00
8147
+ - role: implemented
8148
+ harness: Codex
8149
+ model: GPT-6
8150
+ at: 2026-10-04 09:21 +07:00
8062
8151
  ---
8063
8152
 
8064
8153
  # Native IO
@@ -8286,10 +8375,35 @@ version: carry their reachable relative `.d.ts` closure, rather than emitting re
8286
8375
  instead emit declarations for only that DTO contract. Keep the library's manifest at runtime
8287
8376
  `dependencies: {}` and `peerDependencies: { zod: ... }` when that is its declared promise.
8288
8377
 
8289
- Darwin consumers need the published native binaries at the bundled loader's relative native
8290
- directory. A JS-only bundle is not portable evidence. Qualify the packed library outside its
8291
- build tree in Bun and Node, test its declarations, and check that importing an unbundled
8292
- Stitchkit leaf fails there. A separate schema-only entry must not import the native entry.
8378
+ The packaged Darwin loader has static references to both architecture addons. Bun compilation
8379
+ embeds the matching `.node` asset; ordinary Bun bundling emits native assets alongside its JS
8380
+ output. Publish **every output returned by `Bun.build`**, preserving relative paths. Copying only
8381
+ the JS file drops its native dependency. No runtime Stitchkit installation or manually chosen
8382
+ native path is required for these bundled artifacts.
8383
+
8384
+ ```sh
8385
+ bun build src/native.ts --target=bun --minify --outdir=dist
8386
+ bun build src/native.ts --compile --bytecode --format=esm --outfile=dist/native
8387
+ ```
8388
+
8389
+ For a JS build that previously used `--outfile=dist/native.js`, use
8390
+ `--outdir=dist --entry-naming=native.js` instead: the output can now include native assets.
8391
+ The standalone executable still uses `--outfile` because those assets are embedded inside it.
8392
+
8393
+ Qualify the complete output outside its build tree and installed dependency graph. Test process
8394
+ identity, live-owner refusal, dead-owner recovery and contained file operations in the resulting
8395
+ JS bundle and standalone executable. Bun and Node package imports retain the same lazy loader;
8396
+ importing a portable leaf does not load a Darwin addon on another OS. Test declarations too,
8397
+ and check that importing an unbundled Stitchkit leaf fails in a deliberately isolated library
8398
+ distribution. A separate schema-only entry must not import the native entry.
8399
+
8400
+ If the Darwin backend cannot load, `observeProcessInstance` still returns `unavailable`.
8401
+ Its backend `Error` preserves the original `cause`; JSON serialization carries only the stable
8402
+ `DARWIN_BACKEND_UNAVAILABLE` code, architecture, safe message and failure stage
8403
+ (`architecture`, `resolve`, `load` or `surface`), plus a recognized native error code when present.
8404
+ It excludes stack, paths and nested cause. This backend diagnosis does not reclassify kernel
8405
+ failures or turn unavailable identity into proof that an owner is dead. If a runtime provides
8406
+ no recognized native error code, the stage stays `load`; the loader never infers errno from text.
8293
8407
 
8294
8408
  This delivery removes a runtime kernel installation; it does not remove the build-time
8295
8409
  Stitchkit installation or create an independent lightweight npm package.
@@ -8454,7 +8568,7 @@ description: Executable patterns for moving database, poller, queue and operatio
8454
8568
  type: architecture
8455
8569
  status: active
8456
8570
  created: 2026-08-24
8457
- updated: 2026-08-24
8571
+ updated: 2026-10-04 13:28 +07:00
8458
8572
  ---
8459
8573
 
8460
8574
  # Application migration recipes
@@ -8488,6 +8602,131 @@ The application still owns the client, ORM configuration, transactions,
8488
8602
  migrations and reconnect policy. The executable recipe deliberately fails after
8489
8603
  allocation and proves one rollback close.
8490
8604
 
8605
+ ## Stateful clients, including MTProto
8606
+
8607
+ An application-owned client is an ordinary managed resource. Keep SDK creation,
8608
+ credentials, authorization and reconnect policy in the application; no Telegram
8609
+ client SDK is required by Stitchkit. Create a fresh client inside each `start`,
8610
+ publish it after connection/readiness succeeds, and share one memoized destroy
8611
+ promise between partial-start cleanup, `close` and `force`:
8612
+
8613
+ ```ts
8614
+ let generation: { destroy(): Promise<void> } | undefined
8615
+
8616
+ const client = defineManagedResource({
8617
+ id: 'client',
8618
+ async start({ signal }) {
8619
+ const connection = createClient() // Your SDK and configuration.
8620
+ let destruction: Promise<void> | undefined
8621
+ const destroy = () => destruction ??= Promise.resolve()
8622
+ .then(() => connection.destroy())
8623
+ generation = { destroy }
8624
+ let ready = false
8625
+ let startFailure: unknown
8626
+ let startFailed = false
8627
+ let cleanupFailure: unknown
8628
+ let cleanupFailed = false
8629
+ try {
8630
+ await connection.connect(signal)
8631
+ await connection.assertReady()
8632
+ signal.throwIfAborted()
8633
+ ready = true
8634
+ } catch (error) {
8635
+ startFailure = error
8636
+ startFailed = true
8637
+ } finally {
8638
+ if (!ready) {
8639
+ try { await destroy() }
8640
+ catch (cleanup) {
8641
+ cleanupFailure = cleanup
8642
+ cleanupFailed = true
8643
+ }
8644
+ }
8645
+ }
8646
+ if (startFailed) {
8647
+ if (cleanupFailed) throw new AggregateError([startFailure, cleanupFailure],
8648
+ 'Client startup and cleanup failed', { cause: startFailure })
8649
+ throw startFailure
8650
+ }
8651
+ return { value: connection }
8652
+ },
8653
+ close: () => generation?.destroy(),
8654
+ force: () => generation?.destroy(),
8655
+ })
8656
+
8657
+ const api = defineManagedResource({
8658
+ id: 'api',
8659
+ dependsOn: [client],
8660
+ start(context) {
8661
+ const connection = context.use(client)
8662
+ return { value: async () => {
8663
+ const lease = context.admission.acquire()
8664
+ if (!lease) throw new ApplicationAdmissionError()
8665
+ try { return await connection.invoke() }
8666
+ finally { lease.release() }
8667
+ } }
8668
+ },
8669
+ })
8670
+ ```
8671
+
8672
+ `createClient` supplies a client whose `destroy` safely cleans partial connection
8673
+ setup; its adapter must honor cancellation or otherwise reconcile late SDK effects.
8674
+ The destroy cache belongs to one generation and is reset by the next `start`.
8675
+ Successful startup keeps the connection alive. Dependants start after readiness,
8676
+ requests acquire admission before invoking the client, and dependent HTTP resources
8677
+ close before the client. A subtree restart builds a new client and new dependants.
8678
+
8679
+ The packed-consumer fixture
8680
+ [`managed-client-composition.mjs`](../../packages/core/scripts/consumer-lane/fixtures/minimal/src/managed-client-composition.mjs)
8681
+ proves readiness/admission, partial startup, close order, forced cleanup and restart
8682
+ with a fake client on Bun and Node. It does not claim a live provider acceptance test.
8683
+
8684
+ ## Custom StateStore transaction guards
8685
+
8686
+ Transitions now receive `(current, context)`. Keep ordinary callbacks that only use
8687
+ `current`; update custom store implementations to pass a real protected context.
8688
+ For an in-memory test store, queue transactions and invalidate the context when
8689
+ the transaction finishes:
8690
+
8691
+ ```ts
8692
+ function memoryStore<T>(): StateStore<T> {
8693
+ let state: T | null = null
8694
+ let tail: Promise<unknown> = Promise.resolve()
8695
+ return {
8696
+ read: async () => state,
8697
+ update(transition) {
8698
+ const work = tail.then(async () => {
8699
+ let active = true
8700
+ const context: StateStoreUpdateContext = {
8701
+ async assertHeld() {
8702
+ if (!active) throw new Error('Transaction is no longer active')
8703
+ },
8704
+ }
8705
+ try {
8706
+ const next = await transition(state, context)
8707
+ await context.assertHeld()
8708
+ state = next.state
8709
+ return next.result
8710
+ } finally { active = false }
8711
+ })
8712
+ tail = work.catch(() => undefined)
8713
+ return work
8714
+ },
8715
+ }
8716
+ }
8717
+ ```
8718
+
8719
+ This recipe is process-local and has no restart durability. A database store must
8720
+ guard its real transaction/generation and commit atomically; an escaped or lost
8721
+ guard must refuse. Filesystem mutations made inside inbox transactions call
8722
+ `assertHeld` immediately before publication or cleanup.
8723
+
8724
+ For `createFileStateStore`, remove `staleLockMs` and `retryMs` from configuration.
8725
+ `lockTimeoutMs` remains the acquisition budget. Timestamp age no longer revokes
8726
+ a live or unknown writer. Stop and verify old writers before recovering a legacy
8727
+ pid-only lock; do not invent owner metadata. Historical temporary files are no
8728
+ longer deleted by age. See the [durable intake contract](application-kernel.md#durable-process-facts-and-owner-notifications).
8729
+
8491
8730
  ## Long-running poller
8492
8731
 
8493
8732
  Return separate readiness and completion promises. Readiness says dependants may
@@ -8727,6 +8966,46 @@ It exists for what the other three surfaces cannot do: a generation kicked off
8727
8966
  with `Bash(run_in_background)` that notifies on exit, a `SKILL.md` that shells
8728
8967
  out in one line, a pipeable terminal command.
8729
8968
 
8969
+ ## Installation and types
8970
+
8971
+ Install `stitchkit` and `zod` for a CLI. The public `stitchkit/cli` runtime and
8972
+ declarations do not require `ai`, the MCP SDK or Bun globals. Node consumers can
8973
+ use strict `NodeNext` resolution with `skipLibCheck: false` and ordinary Node
8974
+ types; the package keeps its required dependencies separate from optional SDKs.
8975
+
8976
+ CLI runtime tools use the shared executable contract: identity, input and output
8977
+ schemas, handler, and explicit CLI exposure. SDK presenters belong to the MCP/AI
8978
+ definitions in `stitchkit/tools`, which require the corresponding peers. A typed
8979
+ definition returned by `defineRuntimeTool` or `createRuntimeToolFactory` can be
8980
+ passed directly to both CLI and SDK mounts.
8981
+
8982
+ Keep transport-specific `present` on that typed SDK definition, rather than on
8983
+ an inline object in `CliConfig.runtimeTools`. CLI never executes those presenters;
8984
+ its neutral configuration does not validate SDK presentation. Native CLI command
8985
+ presentation remains on `defineCliCommand({ present })` and retains its inferred
8986
+ output type.
8987
+
8988
+ For a CLI-only managed tool, use the neutral generic construction contract to
8989
+ infer the handler from its schemas:
8990
+
8991
+ ```ts
8992
+ import type { RuntimeToolExecutionWithOutput } from 'stitchkit/cli'
8993
+ import { z } from 'zod'
8994
+
8995
+ const input = z.object({ text: z.string() })
8996
+ const output = z.object({ size: z.number() })
8997
+ const measure = {
8998
+ name: 'measure', description: 'Measure text',
8999
+ identity: { serviceName: 'text', action: 'measure', method: 'POST' },
9000
+ transports: ['CLI'], input, output,
9001
+ handler: ({ input }) => ({ size: input.text.length }),
9002
+ } satisfies RuntimeToolExecutionWithOutput<typeof input, typeof output>
9003
+ ```
9004
+
9005
+ Pass `measure` to `runtimeTools`. The heterogeneous registration type preserves
9006
+ the definition without inventing a common handler input; execute it through
9007
+ `createCli` or `createCliInvoker` so validation remains on the canonical path.
9008
+
8730
9009
  ## Exposure is opt-in
8731
9010
 
8732
9011
  Unlike MCP and agent — where an endpoint with no `expose` is a tool by default —
@@ -8907,6 +9186,16 @@ there stays a positional, and `--` makes a literal `false` one too. The same
8907
9186
  holds for a short alias, a framework flag such as `--json false` and an
8908
9187
  application option.
8909
9188
 
9189
+ Every declared non-array option is single-use, including framework and
9190
+ application globals. Bare, separate-value, inline, negative and short-alias
9191
+ forms of a command option count as the same name: `--active=false --active`,
9192
+ `--active false --no-active` and `-f --active` are argument errors even when
9193
+ the values agree. Duplicates are refused before command execution. Array
9194
+ options accumulate across long and short forms; unknown non-dotted passthrough
9195
+ options retain their raw-list grammar. Tokens after `--` are literal data.
9196
+ Command `--help` and `-h` keep their diagnostic precedence over malformed
9197
+ operation arguments; application globals are validated before routing.
9198
+
8910
9199
  Without presentation configuration, positional arguments fill non-boolean
8911
9200
  fields in declaration order, so `myapp generate "a fox"` is
8912
9201
  `--prompt "a fox"`. A piped value fills the first required unset field:
@@ -9357,6 +9646,88 @@ manifest URL is configuration you wrote, while an asset URL arrives inside a
9357
9646
  document the endpoint served, and a document is exactly what an SSRF boundary
9358
9647
  exists to distrust.
9359
9648
 
9649
+ ### Publishing a complete version
9650
+
9651
+ `publishCli` is an opt-in publisher for a local application-owned distribution
9652
+ directory. It commits every target and its version manifest before atomically
9653
+ replacing the public `manifest.json`. Your application selects the CLI source
9654
+ identity and admits that snapshot at each phase:
9655
+
9656
+ ```ts
9657
+ import { publishCli } from 'stitchkit/cli'
9658
+
9659
+ const result = await publishCli({
9660
+ storageRoot: '/srv/myapp/cli',
9661
+ name: 'myapp', version: cliVersion, commit: selectedCliCommit,
9662
+ baseUrl: 'https://downloads.example.com/cli/',
9663
+ targets: [{ platform: 'linux', arch: 'x64' }],
9664
+ signal,
9665
+ admit: ({ phase, identity, signal }) => assertSelectedSource({ phase, identity, signal }),
9666
+ build: ({ target, stamp, signal }) => compileSelectedSource({ target, stamp, signal }),
9667
+ })
9668
+ // result.outcome: 'published' | 'existing'; result.manifest is ready to serve.
9669
+ ```
9670
+
9671
+ The builder returns `Uint8Array` or `ReadableStream<Uint8Array>` containing the
9672
+ executable. Every target receives the same immutable `{ version, commit,
9673
+ builtAt }` stamp; carry it inside the binary. The publisher owns staging,
9674
+ gzip assets, paths and manifests. Callbacks receive the cancellation signal and
9675
+ run under the publication lock. Admission phases are `prepare`, each target's
9676
+ `build`, `commit`, and `promote`; validate the selected source through commit
9677
+ and promotion. A backend release can explicitly select an unchanged older CLI
9678
+ identity. The helper never substitutes a different commit or rebuilds an
9679
+ existing version under a new identity.
9680
+
9681
+ Assets live at `<version>/<name>-<platform>-<arch>.gz`, with an immutable
9682
+ `<version>/manifest.json` and a public `manifest.json`. A verified repeat keeps
9683
+ the exact bytes and `builtAt`, requires the same target set and base URL, and
9684
+ returns `existing` without invoking the builder. A version committed before a
9685
+ failed promotion is recovered and promoted without rebuilding. Conflicting
9686
+ same-version commits, downgrades, unsafe components, corrupt manifests and
9687
+ assets are refused. Version/name/target components are validated before any
9688
+ build; `storageRoot` is absolute and its existing parent must be trusted.
9689
+
9690
+ All limits are finite positive safe integers; `limits` overrides these defaults:
9691
+
9692
+ | Field | Default | Enforced boundary |
9693
+ | --- | --- | --- |
9694
+ | `maxTargets` | 16 | Requested and verified target count |
9695
+ | `maxManifestBytes` | 256 KiB | Read and written manifest bytes |
9696
+ | `maxAssetBytes` | 256 MiB | Builder and decompressed executable bytes |
9697
+ | `maxCompressedBytes` | 256 MiB | Archived asset bytes |
9698
+ | `maxDirectoryEntries` | 128 | Version layout, root scan and commit space |
9699
+ | `maxStoredVersions` | 16 | Verified publisher-owned history |
9700
+ | `lockTimeoutMs` | 10,000 | Wait to acquire the lock |
9701
+ | `timeoutMs` | 600,000 | Whole publication deadline |
9702
+
9703
+ `retention` defaults to 2 and keeps the current and previous published versions.
9704
+ It must be at least 2 and less than `maxStoredVersions` (at least 3), leaving a
9705
+ recovery slot. Older versions are pruned only after their own identity, exact
9706
+ layout, raw size and digest are verified. Runtime folders and foreign semver
9707
+ directories remain untouched. A full or unverifiable store refuses further
9708
+ publication; repeated completed candidates stay inside the owned history cap.
9709
+ Cancelled or failed incomplete staging is removed only while its root and
9710
+ directory generations still match. The shared asynchronous gzip decoder also
9711
+ bounds updater expansion by the declared raw size and configured byte cap.
9712
+
9713
+ The publisher rejects symlink roots/versions/assets, non-regular files and
9714
+ multiply linked files, and checks held-lock, root and prior-pointer generations
9715
+ before commit and promotion. This is cooperative publication under a trusted
9716
+ parent directory. Portable path checks do not provide an atomic comparison with
9717
+ a hostile process replacing ancestors between syscalls. Durability uses file
9718
+ and directory sync on POSIX; Windows publication is refused. A deadline stops
9719
+ callback/stream waits and blocks late promotion; trusted callbacks remain
9720
+ responsible for cancelling their own subprocesses and external effects.
9721
+
9722
+ A failure after an immutable commit reports that the publication outcome must
9723
+ be checked. A failure after public-pointer rename may already have published;
9724
+ `AtomicFilePublicationError.published` remains available when the atomic writer
9725
+ reports that boundary. Read the durable manifest and retry the admitted same
9726
+ identity to recover; an error does not promise rollback. Optional `signing` and
9727
+ `trust` reuse the Ed25519 contract below. The generated shell installer checks
9728
+ raw digest and size; the updater checks manifest authorship when given a trust
9729
+ root.
9730
+
9360
9731
  ### Proving who built it — signing the manifest
9361
9732
 
9362
9733
  The asset digest proves the bytes that arrived are the bytes the manifest named.
@@ -9595,6 +9966,10 @@ const discovered = await mountConnections([
9595
9966
  transport: { url },
9596
9967
  token: () => key,
9597
9968
  transports: ['CLI'], // the opt-in: this server's tools are commands
9969
+ limits: {
9970
+ discovery: { timeoutMs: 30_000, maxResponseBytes: 4 * 1024 * 1024 },
9971
+ call: { timeoutMs: 120_000 },
9972
+ },
9598
9973
  }),
9599
9974
  ])
9600
9975
  await createCli({ name: 'myapp', version, runtimeTools: discovered, commands: [...] })
@@ -9608,6 +9983,35 @@ One unconvertible schema no longer takes the connection down with it. The tool
9608
9983
  is skipped and **named** (`onSkippedTool`, or a stderr line by default), so a
9609
9984
  surface of two hundred tools is not lost to one.
9610
9985
 
9986
+ MCP connection limits follow the operation phase. `initialize`, the initialized
9987
+ notification, `tools/list` and legacy endpoint discovery use `limits.discovery`;
9988
+ `tools/call` uses `limits.call`. A mounted call's handshake still uses discovery
9989
+ limits. Each logical operation has one deadline, including initial transport
9990
+ negotiation and response-body reading. Legacy endpoint readiness has a discovery
9991
+ deadline; the lifetime of an established SSE stream does not.
9992
+
9993
+ Omitted phase fields inherit the existing connection-level `timeoutMs` and
9994
+ `maxResponseBytes`, whose defaults are 30 seconds and 1 MiB. Values must be positive
9995
+ safe integers; `timeoutMs` must also fit the runtime timer range. The byte ceiling
9996
+ counts raw UTF-8 body bytes, including SSE framing, regardless of `Content-Length`.
9997
+ Pending legacy calls also count comments and wrong-id frames. `budget.maxTools`
9998
+ and `budget.maxSchemaBytes` remain separate mount limits.
9999
+
10000
+ Only the initial `initialize` can negotiate HTTP-to-legacy fallback on 400, 404
10001
+ or 405. A failed `tools/call` is not replayed. Caller cancellation preserves its
10002
+ original reason for in-process observers.
10003
+
10004
+ Expected local failures produce safe tool errors: `CONNECTION_TIMEOUT` (exit 7),
10005
+ `CONNECTION_RESPONSE_TOO_LARGE` (exit 1), `UNAUTHORIZED` (exit 2), `FORBIDDEN`
10006
+ (exit 3) or `CONNECTION_REQUEST_FAILED` (exit 1). Deadline and response-limit
10007
+ failures include fixed `operation`/`phase`, the applicable bound and measured
10008
+ `observedReadBytes`. The public `ConnectionTimeoutError` and
10009
+ `ConnectionResponseTooLargeError` classes carry the same diagnostics. Raw URL,
10010
+ credential, body, identity and stack stay with the original in-process cause in
10011
+ `onToolError` / `afterToolCall`; tool errors produce no process console dump.
10012
+ Unknown throws remain `INTERNAL_SERVER_ERROR`. A timeout is not proof that a
10013
+ write did not happen: verify the remote outcome before repeating an operation.
10014
+
9611
10015
  **A remote refusal keeps its code.** A failed `tools/call` used to become a
9612
10016
  one-sentence `Error` with the result discarded, which cost three things at once:
9613
10017
  the code (so `exitCodes` had nothing to map and every remote failure exited `1`),
@@ -11725,12 +12129,15 @@ error round-trips without adding correlation data to the response body.
11725
12129
  A `code` is a free string — your app codes (`BOT_NOT_FOUND`, …) are yours and the
11726
12130
  core never models them (ADR 0002). But stitchkit itself emits a set of its own:
11727
12131
  `BAD_REQUEST`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `METHOD_NOT_ALLOWED`,
11728
- `CONFLICT`, `RATE_LIMITED`, `VALIDATION_ERROR`, `FILE_INVALID_PATH`,
12132
+ `CONFLICT`, `RATE_LIMITED`, `REQUEST_ABORTED`, `REQUEST_TIMEOUT`,
12133
+ `VALIDATION_ERROR`, `FILE_INVALID_PATH`,
11729
12134
  `FILE_OUTSIDE_ROOT`, `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`,
11730
12135
  `FILE_INSPECTION_REJECTED`, `FILE_TOO_LARGE`, `FILE_EXISTS`,
11731
12136
  `REALTIME_CONTRACT_VIOLATION`, `APPLICATION_NOT_ACCEPTING`, `NOT_IMPLEMENTED`,
11732
12137
  `WAIT_TIMEOUT`,
11733
12138
  `WAIT_FAILED`, `DOWNLOAD_NOT_FOUND`, `VIEW_HTTP_ERROR`,
12139
+ `CONNECTION_TIMEOUT`, `CONNECTION_RESPONSE_TOO_LARGE`,
12140
+ `CONNECTION_REQUEST_FAILED`, `UPSTREAM_TOOL_ERROR`,
11734
12141
  `OPERATION_NOT_SUCCEEDED`, `STREAM_ITEM_INVALID`, `STREAM_FRAME_TOO_LARGE`,
11735
12142
  `STREAM_TERMINAL_MISSING`, `STREAM_LIFETIME_EXCEEDED`,
11736
12143
  `GRAMMY_WEBHOOK_NOT_ACCEPTING`, `SANDBOX_UNAVAILABLE`, `SANDBOX_INSUFFICIENT`,
@@ -14183,7 +14590,8 @@ const report = await runTelegramBroadcast({
14183
14590
  `retry_after` and sends the same recipient again; a server error is retried
14184
14591
  with backoff up to `maxAttempts`, then `failed`. A message Telegram cannot
14185
14592
  parse is not the recipient's fault: the run **halts** with them still pending,
14186
- so the run after the fix reaches them. So does Telegram being unreachable.
14593
+ so the run after the fix reaches them. An unknown transport outcome becomes
14594
+ `uncertain` immediately and halts the run; resume never sends to that recipient again.
14187
14595
  - **Pacing** is `ratePerSecond`, 25 by default. `dryRun` counts and writes
14188
14596
  nothing. One runner per name: a second concurrent run is refused.
14189
14597
 
@@ -14192,6 +14600,66 @@ application (the application's signal, with admission held by the caller).
14192
14600
  The recipient state contains Telegram ids: keep it in the state directory, out
14193
14601
  of git.
14194
14602
 
14603
+ ### Injecting another transport's failure policy
14604
+
14605
+ `classify(error)` returns the public `TelegramBroadcastFailure` union. It lets
14606
+ an application use its own client without adding that SDK to Stitchkit:
14607
+
14608
+ ```ts
14609
+ const report = await runTelegramBroadcast({
14610
+ name: 'announcement',
14611
+ directory: env.STATE_DIRECTORY,
14612
+ recipients: () => audience.activeSubscribers(),
14613
+ send: ({ recipient }) => client.sendMessage(recipient, message),
14614
+ classify(error) {
14615
+ if (isFloodWait(error))
14616
+ return { kind: 'retry-after', retryAfterMs: error.seconds * 1000,
14617
+ reason: 'provider-rate-limit' }
14618
+ if (isRecipientGone(error))
14619
+ return { kind: 'permanent', recipientUnreachable: true,
14620
+ reason: 'recipient-gone' }
14621
+ return { kind: 'ambiguous', reason: 'unknown-provider-outcome' }
14622
+ },
14623
+ maxRetryDelayMs: 60_000,
14624
+ signal: stop.signal,
14625
+ })
14626
+ ```
14627
+
14628
+ The four variants are `retry-after` (exact `retryAfterMs`), `transient` (bounded
14629
+ backoff), `permanent` (optionally `recipientUnreachable` or `stopBroadcast`), and
14630
+ `ambiguous`. Retry variants certify that the failed attempt had no external
14631
+ effect. Lack of an HTTP status does not establish that fact: a server may have
14632
+ accepted the message before the connection failed. `ambiguous` durably records
14633
+ `uncertain`, then halts; resuming the name skips that recipient.
14634
+
14635
+ `maxRetryDelayMs` defaults to 60 seconds. A provider wait above it halts with the
14636
+ recipient pending; it is never shortened to send early. `maxAttempts` is an
14637
+ integer from 1 to 100, `maxRetryDelayMs` is an integer from 0 to 2,147,483,647,
14638
+ and pacing/progress intervals must fit native timers. The optional
14639
+ `sleep(milliseconds, signal)` receives cancellation; the runner also stops waiting
14640
+ when the signal aborts if a custom sleeper ignores it. A send already in flight
14641
+ finishes and is recorded before stopping.
14642
+
14643
+ Without `classify`, known Bot API 429, unreachable-recipient, malformed-message
14644
+ and server-error behavior remains. Unknown/no-status failures now halt as
14645
+ uncertain, rather than being automatically retried. Existing custom `send`
14646
+ implementations that relied on those retries must supply an explicit classifier
14647
+ for proven pre-effect failures. `report.halt` now carries
14648
+ `TelegramBroadcastFailure`; inspect `halt.kind` instead of the old send-failure
14649
+ `scope`/`retryable` fields.
14650
+
14651
+ The runner uses the shared process-instance lock and checks its generation before
14652
+ sends and journal writes. The audience and initial journal directory entry are
14653
+ synced before sending. Lost ownership cannot write a stale delivered receipt or
14654
+ start another send; the persisted intent becomes uncertain on recovery. Old
14655
+ pid-only broadcast locks have unknown ownership and require recovery after their
14656
+ writers are verified stopped, rather than automatic age-based deletion.
14657
+
14658
+ The installed
14659
+ [`telegram-broadcast-classifier.mjs`](../../packages/core/scripts/consumer-lane/fixtures/minimal/src/telegram-broadcast-classifier.mjs)
14660
+ fixture checks injected rate limits, ambiguity/no resend, wait budgets and abort
14661
+ on Bun and Node without a client SDK.
14662
+
14195
14663
  ## Message markup
14196
14664
 
14197
14665
  `stitchkit/telegram/html` is Telegram's HTML parse mode as a tree, with no peer
@@ -14788,6 +15256,123 @@ analytics policy belong to the application.
14788
15256
 
14789
15257
  # Upgrading stitchkit
14790
15258
 
15259
+ ## Released migration: 0.104.0
15260
+
15261
+ ### CLI runtime-tool construction
15262
+
15263
+ **Who must act:** callers writing a schema-dependent handler or MCP/AI `present` directly
15264
+ inside an annotated `CliConfig.runtimeTools` or `CliInvokerConfig.runtimeTools` object.
15265
+ Native `commands` and values returned by `defineRuntimeTool` / `createRuntimeToolFactory`
15266
+ remain valid. Execution, auth, validation and the invocation result envelope keep their behavior.
15267
+
15268
+ Construct a CLI-only managed definition with the neutral schema-aware type before registration:
15269
+
15270
+ ```ts
15271
+ import type { CliInvokerConfig, RuntimeToolExecutionWithOutput } from 'stitchkit/cli'
15272
+ import { z } from 'zod'
15273
+
15274
+ const input = z.object({ text: z.string() })
15275
+ const output = z.object({ size: z.number() })
15276
+ // Before: handler receives the heterogeneous registry context.
15277
+ const config: CliInvokerConfig = {
15278
+ name: 'app',
15279
+ runtimeTools: [{
15280
+ name: 'measure', description: 'Measure text',
15281
+ identity: { serviceName: 'text', action: 'measure', method: 'POST' },
15282
+ transports: ['CLI'], input, output,
15283
+ handler: ({ input }) => ({ size: String(input.text).length }),
15284
+ }],
15285
+ }
15286
+ // After: handler input and output derive from the construction schemas.
15287
+ const measure = {
15288
+ name: 'measure', description: 'Measure text',
15289
+ identity: { serviceName: 'text', action: 'measure', method: 'POST' },
15290
+ transports: ['CLI'], input, output,
15291
+ handler: ({ input }) => ({ size: input.text.length }),
15292
+ } satisfies RuntimeToolExecutionWithOutput<typeof input, typeof output>
15293
+ const nextConfig: CliInvokerConfig = { name: 'app', runtimeTools: [measure] }
15294
+ ```
15295
+
15296
+ For a shared MCP/AI tool, construct the definition with `defineRuntimeTool` from
15297
+ `stitchkit/tools`, including its typed SDK `present` callbacks, then pass the returned value
15298
+ to `runtimeTools`. Install the corresponding SDK peers for that integration. A CLI-only
15299
+ consumer needs `stitchkit`, `zod` and its ordinary Node type tooling.
15300
+
15301
+ `RuntimeToolExecution` names the heterogeneous registration contract. Call a registered
15302
+ operation through `createCli` or `createCliInvoker`; its erased handler input is `never`
15303
+ because the managed runner owns schema validation before execution. For a managed void
15304
+ definition, use `RuntimeToolDefinitionWithoutOutput<typeof input>`.
15305
+
15306
+ ### MCP construction, state stores and delivery
15307
+
15308
+ **Who must act:** schema-dependent runtime tool literals written directly inside MCP mount
15309
+ configuration, MCP presenters returning malformed content or metadata, custom `StateStore`
15310
+ implementations, file-store consumers using retired lock
15311
+ options, broadcast consumers reading `halt` or retrying unknown send outcomes, and consumers
15312
+ that validate HTTP error envelopes with a closed schema. Applications collecting raw tool
15313
+ causes from `console.error` must configure an internal tool hook sink.
15314
+
15315
+ MCP-only code can import the existing server factories from `stitchkit/tools/mcp` and
15316
+ construct its schema-aware tool before heterogeneous registration:
15317
+
15318
+ ```ts
15319
+ import { buildMcpServer, type RuntimeMcpToolDefinitionWithOutput } from 'stitchkit/tools/mcp'
15320
+ import { z } from 'zod'
15321
+ const input = z.object({ value: z.number() })
15322
+ const output = z.object({ doubled: z.number() })
15323
+ // Before: runtimeTools: [{ input, output, handler, present }]
15324
+ const double = {
15325
+ name: 'double', description: 'Double a number',
15326
+ identity: { serviceName: 'numbers', action: 'double', method: 'POST' },
15327
+ input, output,
15328
+ handler: ({ input }) => ({ doubled: input.value * 2 }),
15329
+ present: { mcp: ({ doubled }) => ({ content: [{ type: 'text', text: String(doubled) }] }) },
15330
+ } satisfies RuntimeMcpToolDefinitionWithOutput<typeof input, typeof output>
15331
+ const server = buildMcpServer({ serverInfo: { name: 'numbers', version: '1' }, services: [], runtimeTools: [double] })
15332
+ ```
15333
+
15334
+ Full MCP/AI integrations keep `defineRuntimeTool` from `stitchkit/tools`; those typed values
15335
+ remain accepted by the MCP leaf and CLI. The leaf requires the MCP SDK and Zod, with no AI peer.
15336
+
15337
+ `RuntimeMcpPresentation` preserves the official SDK's required `content` and typed `_meta`
15338
+ fields. A presenter returning `{}` must return `{ content: [] }`; text blocks require string
15339
+ text, and metadata must be an object. `structuredContent` and `isError` remain framework-owned.
15340
+ This restores SDK checks that an indexed mapped type previously erased.
15341
+
15342
+ Unexpected tool causes remain in `hooks.onToolError` and the `error` field of
15343
+ `hooks.afterToolCall`. Protocol stderr carries safe errors. MCP resolver failures use those
15344
+ same observers without executing the handler or its lifecycle again. HTTP's default
15345
+ unexpected-error logging retains its behavior.
15346
+
15347
+ A custom state adapter's `update` must invoke `transition(current, context)` while it still
15348
+ owns the serialized transaction. `context.assertHeld()` checks the actual transaction or lock
15349
+ generation and refuses after completion, rollback or ownership loss. A no-op guard cannot
15350
+ satisfy this contract. Existing transition callbacks may keep one parameter. For the owning
15351
+ file adapter, replace `{ staleLockMs, retryMs, lockTimeoutMs }` with `{ lockTimeoutMs }`:
15352
+ the remaining budget controls acquisition waiting, never expiry of a live transaction.
15353
+ Unknown legacy PID locks and historical temporary files are preserved; recover them only
15354
+ after independently proving their owner is gone.
15355
+
15356
+ Broadcast consumers change `report.halt.reason` checks to the closed `halt.kind` union:
15357
+ `retry-after`, `transient`, `permanent` or `ambiguous`. A custom MTProto adapter can classify
15358
+ an acknowledged flood refusal as `{ kind: 'retry-after', retryAfterMs: wait }`; the exact
15359
+ provider wait is respected. A wait over `maxRetryDelayMs` halts with the recipient pending.
15360
+ Unknown write outcome becomes `uncertain`; restarting the same broadcast does not send it
15361
+ again. A retry classification must certify that the attempt did not apply the external effect.
15362
+
15363
+ HTTP errors gain optional boolean `error.retryable`; framed contract streams use the same
15364
+ field. Update closed consumer schemas before producers declaring it. `false` on 502 and
15365
+ `true` on 409 remain explicit recommendations through the remote tool path. An absent field
15366
+ retains prior status-derived defaults; the field does not change the HTTP client's retry policy.
15367
+ The existing seventh `ApiError` constructor argument remains `ErrorOptions`; retryability is
15368
+ the new eighth argument.
15369
+
15370
+ Caller cancellation now reaches `implementRemote`'s existing HTTP call. Applications bind
15371
+ signals to `createCli` / `createCliInvoker` and cooperative handlers observe `ctx.signal`.
15372
+ `REQUEST_ABORTED` is nonretryable and exits 130. A disconnected caller does not prove that
15373
+ an origin ignoring cancellation or an external provider stopped; keep resource leases until
15374
+ that work actually completes.
15375
+
14791
15376
  ## Released migration: 0.103.0
14792
15377
 
14793
15378
  ### Diagnostic journal startup preserves torn evidence and inspects archives
@@ -19025,7 +19610,7 @@ from the root `stitchkit`.
19025
19610
  | Export | Kind | Summary |
19026
19611
  |--------|------|---------|
19027
19612
  | `AppError` | class | the framework error — `code` / `status` / `details` / `hint` |
19028
- | `ErrorEnvelope` | _type_ | the JSON shape of an error response |
19613
+ | `ErrorEnvelope` | _type_ | the JSON shape of an error response, preserving optional declared boolean `error.retryable` without changing HTTP retry policy |
19029
19614
  | `notFound` | function | throw `404 NOT_FOUND` — [guide](../guide/auth-and-errors.md#throwing-errors) |
19030
19615
  | `badRequest` | function | throw `400 BAD_REQUEST` |
19031
19616
  | `unauthorized` | function | throw `401 UNAUTHORIZED` |
@@ -19188,6 +19773,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
19188
19773
  | `respondJson` | function | a raw route's JSON response (`204` for null/undefined) |
19189
19774
  | `errorResponse` | function | any thrown value → the framework error envelope + `x-request-id` |
19190
19775
  | `normalizeError` | function | any thrown value → an `AppError` (`ZodError` → `VALIDATION_ERROR` 400 with structured `details.issues`, else generic 500) — the framework's canonical classification, for a bespoke `onError` |
19776
+ | `NormalizeErrorOptions` | _type_ | `logUnexpected` defaults to logging the raw server-side cause; tool adapters disable process dumps while their existing observers retain that cause |
19191
19777
  | `errorCode` | function | the stable error code for a thrown value (side-effect-free — for log attribution) |
19192
19778
  | `formatZodError` | function | a `ZodError` → a readable, field-summarised string |
19193
19779
  | `zodIssues` | function | a `ZodError` → structured `{ path, code, message }[]` — the machine-readable sibling of `formatZodError` |
@@ -19404,7 +19990,9 @@ cutovers are covered by the executable
19404
19990
  | `NotificationOutboxItem` / `NotificationOutboxReceipt` / `NotificationOutboxState` / `NotificationSend` / `NotificationFailureClassification` / `DroppedNotification` / `EnqueueNotification` | _type_ | versioned queue records, stable send identity and delivery decisions |
19405
19991
  | `notificationOutboxStateSchema` | function | strict persisted-state schema over an application payload schema |
19406
19992
  | `notificationOutboxResource` / `NotificationOutboxResource` / `NotificationOutboxResourceConfig` | function / _type_ | thin managed-resource scheduler over an outbox handle |
19407
- | `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | managed resource delivering each `<name>.json` entry another program drops into a directory to the application at least once, after readiness; separate durable taken/done records, leases, bounded retry |
19993
+ | `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | one managed directory inbox for files or durable `accept({ source, key, entry })`; first payload wins, bounded capacity/bytes/receipts and renewable fenced leases; delivery begins after readiness and effects remain at least once |
19994
+ | `DirectoryInboxIdentitySchema` / `DirectoryInboxIdentity` / `DirectoryInboxAccept` / `DirectoryInboxAcceptResultSchema` / `DirectoryInboxAcceptResult` | schema / _type_ | validated source/key, typed entry and durable accepted/duplicate result with filename; duplicates do not replace the first payload |
19995
+ | `StateStoreUpdateContext` | _type_ | real active protected transaction: `assertHeld()` refuses after ownership loss or completion; custom adapters supply this context to their transition |
19408
19996
  | `DirectoryInboxStateSchema` / `DirectoryInboxState` / `DirectoryInboxRejection` / `DirectoryInboxRejectionReasonSchema` / `DirectoryInboxRejectionReason` | schema / _type_ | the inbox's claims, receipts and rejections (`invalid`, `too-large`, `attempt-limit`) |
19409
19997
  | `ApplicationHandle.restart` | method | replace one resource and everything that depends on it, leaving the rest of the graph running and the process epoch unchanged |
19410
19998
  | `ApplicationRestartInputSchema` / `ApplicationRestartInput` | schema / _type_ | the resource to replace, by id, and optionally `gracePeriodMs` / `forceTimeoutMs` for this restart — the application's own shutdown budget otherwise |
@@ -20589,6 +21177,7 @@ Advanced building blocks — the shared machinery the mounts are built on.
20589
21177
  | `TransportSummary` | _type_ | `{ contractServices, runtimeTools, totals, sources }` from `summarizeTransports` |
20590
21178
  | `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
20591
21179
  | `ToolSurfaceDefinition` | _type_ | shared object-shaped `{ services?, runtimeTools? }` introspection surface |
21180
+ | `ToolSurfaceProjection` | _type_ | one readonly services/runtime container shared by full and MCP-only surfaces; executable mounts specialize the runtime definition without pulling in another adapter's peers |
20592
21181
  | `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
20593
21182
  | `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
20594
21183
  | `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions into conservative object joins; scalar collisions retain provable types, object/array collisions retain structural alternatives, and field descriptions retain discriminator labels plus availability/requiredness hints; the projection never executes validation |
@@ -20598,6 +21187,22 @@ Advanced building blocks — the shared machinery the mounts are built on.
20598
21187
 
20599
21188
  ---
20600
21189
 
21190
+ ## `stitchkit/tools/mcp`
21191
+
21192
+ A server-only leaf over the same MCP owner as `stitchkit/tools`. Its runtime and strict
21193
+ NodeNext declarations require Zod and `@modelcontextprotocol/server`, with no AI SDK peer.
21194
+ HTTP/stdio factories, mounts, schema policy, catalog and resource helpers keep their canonical
21195
+ behavior and names. See [MCP guide](../guide/mcp-and-agents.md#mcp--createmcphandler).
21196
+
21197
+ | Export | Kind | Purpose |
21198
+ |--------|------|---------|
21199
+ | `createMcpHandler` / `createMcpHttpRoute` / `buildMcpServer` / `mountMcp` | function | the shared authenticated HTTP handler, raw-route adapter, SDK server and contract mount |
21200
+ | `createStdioMcpServer` / `bindStdioProcessSignals` | function | the shared stdio server and bounded process cleanup |
21201
+ | `RuntimeMcpToolDefinitionWithOutput` | _type_ | schema-aware MCP-only construction with concrete handler output and presenter input |
21202
+ | `RuntimeMcpToolDefinition` | _type_ | heterogeneous MCP registration; the canonical runner validates input/output before invocation |
21203
+ | `RuntimeMcpToolPresenters` / `RuntimeMcpPresentation` | _type_ | official SDK content without framework-owned `structuredContent` or `isError`; one presenter owner shared with the full SDK surface |
21204
+ | `RuntimeToolDefinitionWithoutOutput` | _type_ | the shared neutral void definition |
21205
+
20601
21206
  ## `stitchkit/tools/connections`
20602
21207
 
20603
21208
  Server-only subexport that consumes external MCP servers and OpenAPI specifications
@@ -20609,6 +21214,9 @@ and approval path.
20609
21214
  | Export | Kind | Summary |
20610
21215
  |--------|------|---------|
20611
21216
  | `defineMcpClientConnection` | function | declare one external MCP server connection over Streamable HTTP with an optional SSE fallback |
21217
+ | `ConnectionOperationLimits` / `McpConnectionLimits` | _type_ | positive finite deadline/raw byte ceilings with independent discovery/call overrides; omitted fields inherit the shared bounds |
21218
+ | `ConnectionPhase` / `ConnectionOperation` / `ConnectionFailureContext` | _type_ | closed operation/phase vocabulary and measured read context for existing typed connection errors |
21219
+ | `ConnectionTimeoutError` / `ConnectionResponseTooLargeError` | class | bounded operation refusal, with phase, operation and finite observed bytes; safe tool projection retains the raw cause locally |
20612
21220
  | `defineOpenApiConnection` | function | declare one OpenAPI document (object, JSON text or JSON URL) as a tool surface |
20613
21221
  | `mountConnections` | function | discover every connection and return readonly `RuntimeToolDefinition`s for `mountAgent` |
20614
21222
  | `ConnectionAuthorizationRequiredError` | class | typed reauthorization signal raised on `401` with credentials resolved again on the next call |
@@ -20748,8 +21356,9 @@ injected grammY bot. → ADR 0143, ADR 0201 — [guide](../guide/telegram.md)
20748
21356
  | `classifyTelegramSendFailure` | function | name a refused Bot API send and separate "retry this send" from "stop addressing this recipient" |
20749
21357
  | `TelegramSendFailure` | _type_ | reason, `status`, Telegram-stated `retryAfterSeconds`, `retryable`, `recipientUnreachable` and which evidence produced the answer |
20750
21358
  | `TelegramSendFailureReason` / `TELEGRAM_SEND_FAILURE_REASONS` | _type_ / constant | `blocked-by-user` / `user-deactivated` / `chat-not-found` / `not-started` / `rate-limited` / `message-invalid` / `server-error` / `unknown`, and the same as a list; a `Record<TelegramSendFailureReason, …>` over them fails to compile when a reason is added |
20751
- | `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 |
21359
+ | `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 |
20752
21360
  | `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` |
21361
+ | `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 |
20753
21362
  | `telegramBroadcastSender` / `TelegramBroadcastSenderConfig` / `TelegramBroadcastMessage` | function / _type_ | the standard `send`: `sendMessage` with a text or `copyMessage` of a prepared message, through the Bot API |
20754
21363
  | `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 |
20755
21364
  | `TelegramOperatorChannelConfig` / `TelegramOperatorChannel` / `TelegramOperatorMessage` / `TelegramOperatorDrop` / `TelegramChatId` | _type_ | `chatId`, `topics`, `send`, `maxQueued`, `minIntervalMs`, `maxAttempts`, `onDropped`, `sensitivePatterns` and `sensitiveValues` (a bot token is always masked), `dedupe`; a drop is `overflow` / `refused` / `attempts` / `closed` / `repeated` / `over-budget` with its classification |
@@ -21092,6 +21701,7 @@ SDK nor the `ai` peer.
21092
21701
  | `checkCliUpdate` | function | bounded, interval-limited, never-throwing check for a newer published build |
21093
21702
  | `applyCliUpdate` | function | download, verify the decompressed digest and replace the binary by rename |
21094
21703
  | `assertCliPublishable` | function | refuse republishing one version from a different commit — takes one manifest or every manifest already published, because the promise a version makes does not stop at a channel boundary |
21704
+ | `publishCli` / `CliPublicationOptions` / `CliPublicationPhase` / `CliPublicationResult` | function / _type_ | optional bounded builder/admission over one immutable name/version/commit stamp; commit verified assets before the manifest pointer, recover exact completed versions, retain verified history; existing signatures/installer/updater own the wire contract |
21095
21705
  | `rollbackCliUpdate` / `CliRollbackConfig` / `RolledBackCliUpdate` | function / _types_ | put the kept previous build back, digest-checked first and written by the same atomic rename the update uses |
21096
21706
  | `signCliManifest` / `verifyCliManifest` / `cliManifestSigningPayload` | function | Ed25519 over `{name, version, commit, builtAt, assets[]}` — every asset's digest included, so the chain closes on the file that executes |
21097
21707
  | `cliSignatureAccepted` / `CliSignatureVerdict` | function / _type_ | `valid` \| `unenforced` \| `missing` \| `unknown-key` \| `invalid`; `unenforced` keeps an unpinned build updating while making the absence of a check visible |
@@ -21123,6 +21733,9 @@ SDK nor the `ai` peer.
21123
21733
  | `CliConfig` | _type_ | config for `createCli`; `defaultCommand`, `globalOptions`, `optionAliases` and `positionals` define the shared command presentation policy |
21124
21734
  | `CliPresentationPolicyConfig` | _type_ | shared command presentation-policy subset of `CliConfig` |
21125
21735
  | `CliSurfaceSource` | _type_ | static service/runtime array or identity-dependent factory |
21736
+ | `RuntimeToolExecution` | _type_ | neutral heterogeneous runtime-tool registration; handlers execute through the schema-validating managed runner |
21737
+ | `RuntimeToolExecutionWithOutput` | _type_ | strict neutral managed definition with schema-derived handler input/output; construct before adding to CLI `runtimeTools` |
21738
+ | `RuntimeToolDefinitionWithoutOutput` | _type_ | strict neutral managed void definition without an output contract or transport presenters |
21126
21739
  | `CliCommandDefinition` | _type_ | native command definition union |
21127
21740
  | `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
21128
21741
  | `CliCommandDefinitionWithOutput` | _type_ | native command with validated declared output and typed optional `present` / successful `exitCode` callbacks |