stitchkit 0.103.13 → 0.104.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +120 -0
- package/darwin-native.cjs +1 -3
- package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
- package/dist/agent-runtime/store-conformance-support.d.ts +5 -5
- package/dist/application/directory-inbox-accept.d.ts +13 -0
- package/dist/application/directory-inbox-accept.d.ts.map +1 -0
- package/dist/application/directory-inbox-contract.d.ts +42 -5
- package/dist/application/directory-inbox-contract.d.ts.map +1 -1
- package/dist/application/directory-inbox-delivery.d.ts +14 -0
- package/dist/application/directory-inbox-delivery.d.ts.map +1 -0
- package/dist/application/directory-inbox-files.d.ts +28 -0
- package/dist/application/directory-inbox-files.d.ts.map +1 -0
- package/dist/application/directory-inbox-state.d.ts +14 -0
- package/dist/application/directory-inbox-state.d.ts.map +1 -0
- package/dist/application/directory-inbox.d.ts +2 -1
- package/dist/application/directory-inbox.d.ts.map +1 -1
- package/dist/application/file-state-store.d.ts +2 -8
- package/dist/application/file-state-store.d.ts.map +1 -1
- package/dist/application/state-store.d.ts +6 -1
- package/dist/application/state-store.d.ts.map +1 -1
- package/dist/browser/client-arguments.d.ts +4 -0
- package/dist/browser/client-arguments.d.ts.map +1 -0
- package/dist/browser/client-executors.d.ts.map +1 -1
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/contract-stream.d.ts.map +1 -1
- package/dist/browser/http.d.ts +2 -1
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/native-fetch.d.ts +8 -0
- package/dist/browser/native-fetch.d.ts.map +1 -0
- package/dist/contract/errors.d.ts +16 -12
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/normalize.d.ts +5 -1
- package/dist/contract/normalize.d.ts.map +1 -1
- package/dist/contract/stream.d.ts +1 -0
- package/dist/contract/stream.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/browser.js +3 -3
- package/dist/entrypoints/agent-runtime/coding-tools.js +6 -6
- package/dist/entrypoints/agent-runtime/harness-tools.js +8 -8
- package/dist/entrypoints/agent-runtime/harness.js +11 -11
- package/dist/entrypoints/agent-runtime/realtime.js +3 -3
- package/dist/entrypoints/agent-runtime/sandbox.js +6 -6
- package/dist/entrypoints/agent-runtime.js +13 -13
- package/dist/entrypoints/application/diagnostic-journal.js +1 -1
- package/dist/entrypoints/application/directory-inbox.js +424 -119
- package/dist/entrypoints/application/grammy.js +1 -1
- package/dist/entrypoints/application.d.ts +2 -2
- package/dist/entrypoints/application.d.ts.map +1 -1
- package/dist/entrypoints/application.js +7 -3
- package/dist/entrypoints/cli.d.ts +2 -0
- package/dist/entrypoints/cli.d.ts.map +1 -1
- package/dist/entrypoints/cli.js +757 -136
- package/dist/entrypoints/contract.js +3 -3
- package/dist/entrypoints/files/packaging.d.ts +3 -0
- package/dist/entrypoints/files/packaging.d.ts.map +1 -0
- package/dist/entrypoints/files/packaging.js +133 -0
- package/dist/entrypoints/files.js +10 -107
- package/dist/entrypoints/index.js +7 -7
- package/dist/entrypoints/node.js +8 -8
- package/dist/entrypoints/observability.js +2 -2
- package/dist/entrypoints/react.js +1 -1
- package/dist/entrypoints/remote.js +19 -6
- package/dist/entrypoints/server.d.ts +1 -1
- package/dist/entrypoints/server.d.ts.map +1 -1
- package/dist/entrypoints/server.js +23 -17
- package/dist/entrypoints/telegram.d.ts +1 -0
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +214 -98
- package/dist/entrypoints/testing.js +7 -7
- package/dist/entrypoints/tools/connections.d.ts +2 -2
- package/dist/entrypoints/tools/connections.d.ts.map +1 -1
- package/dist/entrypoints/tools/connections.js +427 -294
- package/dist/entrypoints/tools/invoker.js +6 -6
- package/dist/entrypoints/tools/mcp.d.ts +12 -0
- package/dist/entrypoints/tools/mcp.d.ts.map +1 -0
- package/dist/entrypoints/tools/mcp.js +45 -0
- package/dist/entrypoints/tools.d.ts +1 -0
- package/dist/entrypoints/tools.d.ts.map +1 -1
- package/dist/entrypoints/tools.js +82 -1147
- package/dist/entrypoints/tracking.js +1 -1
- package/dist/files/boundary.d.ts +2 -8
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files/file-io.d.ts +0 -5
- package/dist/files/file-io.d.ts.map +1 -1
- package/dist/files/native-packaging-layout.d.ts +12 -0
- package/dist/files/native-packaging-layout.d.ts.map +1 -0
- package/dist/files/native-packaging.d.ts +72 -0
- package/dist/files/native-packaging.d.ts.map +1 -0
- package/dist/files/read.d.ts +2 -2
- package/dist/files/read.d.ts.map +1 -1
- package/dist/index-1a6jtnd9.js +88 -0
- package/dist/index-269zqj8m.js +77 -0
- package/dist/{index-r34mvse1.js → index-27a2pj09.js} +1 -1
- package/dist/{index-r956rndn.js → index-2hyavjjj.js} +3 -3
- package/dist/{index-m2xqkv55.js → index-2vnh05w4.js} +83 -14
- package/dist/{index-pw99k9h5.js → index-43p08g7t.js} +1 -1
- package/dist/{index-xwq9j1sf.js → index-4e0ah203.js} +9 -7
- package/dist/{index-qapzv4p5.js → index-5z6jn897.js} +1 -1
- package/dist/{index-ahvsksw5.js → index-6d9xaxw1.js} +1 -1
- package/dist/{index-8zfsgar0.js → index-6drst9sg.js} +11 -5
- package/dist/{index-x87j7csh.js → index-77nxk6ay.js} +2 -2
- package/dist/{index-dnn9z96q.js → index-a0zzh8ak.js} +3 -3
- package/dist/{index-kmpzj1cq.js → index-a3wma1hk.js} +1 -1
- package/dist/{index-j8aw2c0e.js → index-bakgedh8.js} +35 -7
- package/dist/index-bhb950qs.js +1145 -0
- package/dist/{index-tgzhq07n.js → index-cvda079a.js} +1 -1
- package/dist/{index-n2gre124.js → index-fa4wg312.js} +4 -4
- package/dist/{index-zp4jpkp0.js → index-fk18k56k.js} +2 -2
- package/dist/{index-afjvjyck.js → index-g2j7pnak.js} +2 -134
- package/dist/index-g95fk660.js +116 -0
- package/dist/{index-r5c4fedv.js → index-h2fhjqy7.js} +2 -1
- package/dist/{index-sybedc8b.js → index-hbd32c6a.js} +23 -94
- package/dist/{index-fyphpknh.js → index-hp9d77wk.js} +1 -1
- package/dist/index-jd470k6q.js +109 -0
- package/dist/{index-b2bz0kdm.js → index-kb8styz3.js} +4 -3
- package/dist/{index-0c419yx7.js → index-kemm0ej5.js} +12 -9
- package/dist/{index-db2spkv1.js → index-ptn0g0gj.js} +3 -3
- package/dist/{index-w575evhq.js → index-r4r70zmh.js} +1 -1
- package/dist/{index-12m2v2a9.js → index-s4evrmp5.js} +1 -1
- package/dist/{index-1khy8y7n.js → index-t1sr4aks.js} +3 -3
- package/dist/{index-45tdvqa3.js → index-wh0ecvyk.js} +1 -1
- package/dist/{index-n53mswt3.js → index-x928baz2.js} +8 -1
- package/dist/index-yk4g8805.js +133 -0
- package/dist/{index-ygkka6zn.js → index-ywppsaby.js} +28 -5
- package/dist/{index-9853ymwb.js → index-za6j7nwy.js} +18 -4
- package/dist/internal/bounded-file-read.d.ts +41 -0
- package/dist/internal/bounded-file-read.d.ts.map +1 -0
- package/dist/internal/exclusive-lock.d.ts +2 -0
- package/dist/internal/exclusive-lock.d.ts.map +1 -1
- package/dist/internal/with-exclusive-lock.d.ts +4 -2
- package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
- package/dist/telegram/broadcast-delivery.d.ts +23 -0
- package/dist/telegram/broadcast-delivery.d.ts.map +1 -0
- package/dist/telegram/broadcast-failure.d.ts +22 -0
- package/dist/telegram/broadcast-failure.d.ts.map +1 -0
- package/dist/telegram/broadcast-state.d.ts +6 -3
- package/dist/telegram/broadcast-state.d.ts.map +1 -1
- package/dist/telegram/broadcast.d.ts +10 -5
- package/dist/telegram/broadcast.d.ts.map +1 -1
- package/dist/tools/cli/format.d.ts.map +1 -1
- package/dist/tools/cli/invoke.d.ts +2 -2
- package/dist/tools/cli/invoke.d.ts.map +1 -1
- package/dist/tools/cli/publication-assets.d.ts +10 -0
- package/dist/tools/cli/publication-assets.d.ts.map +1 -0
- package/dist/tools/cli/publication-contract.d.ts +75 -0
- package/dist/tools/cli/publication-contract.d.ts.map +1 -0
- package/dist/tools/cli/publication-control.d.ts +4 -0
- package/dist/tools/cli/publication-control.d.ts.map +1 -0
- package/dist/tools/cli/publication-storage.d.ts +32 -0
- package/dist/tools/cli/publication-storage.d.ts.map +1 -0
- package/dist/tools/cli/publication.d.ts +35 -0
- package/dist/tools/cli/publication.d.ts.map +1 -0
- package/dist/tools/cli/update.d.ts.map +1 -1
- package/dist/tools/connections/define.d.ts.map +1 -1
- package/dist/tools/connections/error-projection.d.ts +4 -0
- package/dist/tools/connections/error-projection.d.ts.map +1 -0
- package/dist/tools/connections/errors.d.ts +24 -8
- package/dist/tools/connections/errors.d.ts.map +1 -1
- package/dist/tools/connections/limits.d.ts +11 -31
- package/dist/tools/connections/limits.d.ts.map +1 -1
- package/dist/tools/connections/mcp-client.d.ts +6 -4
- package/dist/tools/connections/mcp-client.d.ts.map +1 -1
- package/dist/tools/connections/mcp.d.ts.map +1 -1
- package/dist/tools/connections/openapi.d.ts.map +1 -1
- package/dist/tools/connections/operation-limits.d.ts +25 -0
- package/dist/tools/connections/operation-limits.d.ts.map +1 -0
- package/dist/tools/connections/sse-frames.d.ts +11 -2
- package/dist/tools/connections/sse-frames.d.ts.map +1 -1
- package/dist/tools/connections/sse-session.d.ts +15 -2
- package/dist/tools/connections/sse-session.d.ts.map +1 -1
- package/dist/tools/connections/types.d.ts +19 -1
- package/dist/tools/connections/types.d.ts.map +1 -1
- package/dist/tools/execute-result.d.ts.map +1 -1
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/internal/observe-hook.d.ts +3 -0
- package/dist/tools/internal/observe-hook.d.ts.map +1 -0
- package/dist/tools/internal/surface-projector.d.ts +2 -1
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/mcp/mount.d.ts +2 -2
- package/dist/tools/mcp/mount.d.ts.map +1 -1
- package/dist/tools/mcp/prepare.d.ts +4 -5
- package/dist/tools/mcp/prepare.d.ts.map +1 -1
- package/dist/tools/mcp/register.d.ts +4 -1
- package/dist/tools/mcp/register.d.ts.map +1 -1
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/runtime-tool-execution.d.ts +65 -0
- package/dist/tools/runtime-tool-execution.d.ts.map +1 -0
- package/dist/tools/runtime-tool-mcp.d.ts +27 -0
- package/dist/tools/runtime-tool-mcp.d.ts.map +1 -0
- package/dist/tools/runtime-tool.d.ts +9 -60
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +12 -14
- package/dist/tools/surface.d.ts.map +1 -1
- package/llms/application.2.txt +221 -44
- package/llms/application.3.txt +3 -1
- package/llms/application.txt +1 -1
- package/llms/cli.2.txt +144 -1
- package/llms/cli.txt +123 -104
- package/llms/contract.txt +1 -1
- package/llms/files-packaging.txt +27 -0
- package/llms/process.txt +63 -1
- package/llms/server.4.txt +4 -1
- package/llms/server.5.txt +1 -0
- package/llms/stitchkit.2.txt +14 -0
- package/llms/stitchkit.txt +6 -4
- package/llms/telegram.txt +64 -2
- package/llms/tools-connections.txt +3 -0
- package/llms/tools-mcp.txt +29 -0
- package/llms/tools.2.txt +59 -0
- package/llms/tools.3.txt +1 -0
- package/llms/tools.txt +11 -52
- package/llms/upgrading-0.37.0-0.44.0.txt +444 -0
- package/llms/{upgrading-0.44.0-0.60.0.txt → upgrading-0.44.0-0.61.0.txt} +140 -271
- package/llms/{upgrading-0.61.0-0.74.0.txt → upgrading-0.62.0-0.80.0.txt} +217 -140
- package/llms/{upgrading-0.75.0-0.103.0.txt → upgrading-0.81.0-0.104.0.txt} +154 -217
- package/llms/upgrading.txt +4 -4
- package/llms-full.txt +745 -60
- package/llms.txt +14 -12
- package/native-assets.json +8 -0
- package/package.json +12 -3
- package/dist/index-wykebpn9.js +0 -221
- package/llms/upgrading-0.37.0.txt +0 -176
- package/dist/{index-69nvze1q.js → index-pptyv13k.js} +3 -3
package/llms/application.2.txt
CHANGED
|
@@ -122,49 +122,101 @@ deadline holds `stop()` — and so the resource's `force()` — for that one cal
|
|
|
122
122
|
so give the transport a timeout. Superseding a key that is being sent right now
|
|
123
123
|
does not recall it: the send completes, and only its receipt is not written.
|
|
124
124
|
|
|
125
|
-
`createDirectoryInbox`
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
`
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
125
|
+
`createDirectoryInbox` receives entries from another program on the same host,
|
|
126
|
+
or directly from a transport adapter. Import the resource from
|
|
127
|
+
`stitchkit/application/directory-inbox`; its contracts live in
|
|
128
|
+
`stitchkit/application`. Its published handle has `accept({ source, key, entry })`,
|
|
129
|
+
`flush()` and `state()`:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const inbox = createDirectoryInbox({
|
|
133
|
+
id: 'updates',
|
|
134
|
+
directory: env.INBOX_DIRECTORY,
|
|
135
|
+
schema: UpdateSchema,
|
|
136
|
+
handle: ({ identity, entry, signal }) => processUpdate(identity, entry, signal),
|
|
137
|
+
})
|
|
138
|
+
const ingress = defineManagedResource({
|
|
139
|
+
id: 'ingress',
|
|
140
|
+
dependsOn: [inbox],
|
|
141
|
+
start(context) {
|
|
142
|
+
const intake = context.use(inbox)
|
|
143
|
+
return { value: (key: string, entry: Update) =>
|
|
144
|
+
intake.accept({ source: 'client-updates', key, entry }) }
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`accept` is available after the resource starts, including before activation.
|
|
150
|
+
It validates the entry and full producer identity, writes a lossless JSON envelope,
|
|
151
|
+
syncs its file and parent directory, then returns
|
|
152
|
+
`{ status: 'accepted' | 'duplicate', filename }`. Automatic delivery starts at
|
|
153
|
+
`activate`; an explicit `flush()` is available before activation. A producer
|
|
154
|
+
should acknowledge its provider only after acceptance resolves. Failure after
|
|
155
|
+
publication can mean the entry was accepted but its acknowledgement was lost:
|
|
156
|
+
retry the same identity, and `duplicate` keeps the original payload.
|
|
157
|
+
|
|
158
|
+
The identity is the pair `source` (1–128 characters) and `key` (1–1024 characters).
|
|
159
|
+
A hash of the pair names the file; the full pair is retained inside its envelope
|
|
160
|
+
and checked against existing files and records. Two sources may use the same key.
|
|
161
|
+
The handler's legacy `key` remains the filename; programmatic entries additionally
|
|
162
|
+
carry `identity`. No Telegram numeric update id or MTProto event shape is invented.
|
|
163
|
+
|
|
164
|
+
The default `maxPendingEntries: 1000` refuses new programmatic entries when the
|
|
165
|
+
pending directory is full, without publishing another file; duplicate acceptance
|
|
166
|
+
still succeeds. `maxEntryBytes` defaults to 1 MiB and counts the complete UTF-8
|
|
167
|
+
envelope. A completed identity is deduplicated while its receipt is retained
|
|
168
|
+
(`retain`, default 1000); a rejected envelope also deduplicates while its file
|
|
169
|
+
remains in `rejected/`. Choose the retention and rejected-file cleanup policy to
|
|
170
|
+
match the provider's replay window. Provider cursors, identity extraction and
|
|
171
|
+
external-effect idempotency remain application responsibilities.
|
|
172
|
+
|
|
173
|
+
The original file-producer path also remains: atomically publish
|
|
174
|
+
`<name>.json`, using a temporary name followed by a rename. Names starting with
|
|
175
|
+
a dot are ignored; `intake-<64 hex characters>.json` is reserved for programmatic
|
|
176
|
+
envelopes. Entries are delivered in filename order. The reader refuses unsafe
|
|
177
|
+
leaf symlinks, multiple links and files changed during reading.
|
|
178
|
+
`stopAdmission` stops new acceptance and delivery, `drain` waits for accepted
|
|
179
|
+
storage operations and the handler in flight, and `force` aborts its handler
|
|
180
|
+
signal. A handler ignoring that signal may still finish an external effect;
|
|
181
|
+
the inbox leaves its entry unsettled rather than claiming it was cancelled.
|
|
182
|
+
|
|
183
|
+
Taken and done are separate durable records. Before `handle`, the entry is
|
|
184
|
+
claimed with a unique expiring lease (`leaseMs`, default five minutes). A
|
|
185
|
+
long handler renews that lease; an expired or replaced lease cannot complete,
|
|
186
|
+
reject, retry or remove the successor's file. Success first commits a receipt,
|
|
187
|
+
then removes only that file generation under a new protected transaction.
|
|
188
|
+
A crash after receipt commit is recovered by cleanup without redelivery. A crash
|
|
189
|
+
before receipt commit may redeliver: the guarantee is at least once, so use the
|
|
190
|
+
full producer identity as the external idempotency key.
|
|
191
|
+
|
|
192
|
+
A throwing handler retries after a backoff from one second to five minutes.
|
|
193
|
+
Schema failures, oversize files and `maxAttempts` exhaustion (default 20) are
|
|
194
|
+
recorded before their files move to `rejected/`, with `onRejected` reporting the
|
|
195
|
+
reason. The state defaults to `<directory>/.inbox-state.json`, or uses the supplied
|
|
196
|
+
`store`. Manual file producers are responsible for atomic publication and capacity.
|
|
197
|
+
|
|
198
|
+
These primitives share the structural `StateStore`. `update` owns one atomic
|
|
199
|
+
read/modify/write transaction and passes a `StateStoreUpdateContext` as the
|
|
200
|
+
transition's second argument. Its `await context.assertHeld()` checks that the
|
|
201
|
+
transaction is still active and its exclusive generation is still owned. A custom
|
|
202
|
+
store must implement both real serialization/atomic commit and this guard;
|
|
203
|
+
a no-op guard does not satisfy the contract. Ordinary one-argument transition
|
|
204
|
+
callbacks remain valid.
|
|
205
|
+
|
|
206
|
+
On a server, `createFileStateStore` uses the same process-instance lock as
|
|
207
|
+
`withExclusiveLock`, unique staging files and atomic directory-durable publication.
|
|
208
|
+
`lockTimeoutMs` (default 10 s, zero means one attempt) bounds acquisition only.
|
|
209
|
+
A live or unattributable owner is never replaced by age, including after an event
|
|
210
|
+
loop stall or `SIGSTOP`; only a provably dead local process instance is reclaimed.
|
|
211
|
+
Legacy readable pid/token records without a host are unknown owners and need
|
|
212
|
+
operator recovery after the old writer is verified stopped.
|
|
213
|
+
|
|
214
|
+
`staleLockMs` and `retryMs` are removed and are refused at runtime. The current
|
|
215
|
+
write removes its own staging file; unknown historical temporary files are
|
|
216
|
+
preserved, without an age sweep. Directory durability requires a filesystem
|
|
217
|
+
supporting directory sync (Linux and macOS); Windows is refused explicitly.
|
|
218
|
+
Ledger corruption may be declared reconstructable; an outbox must fail closed
|
|
219
|
+
rather than discard pending delivery.
|
|
168
220
|
|
|
169
221
|
## Recovering change subscriptions
|
|
170
222
|
|
|
@@ -232,7 +284,7 @@ description: Executable patterns for moving database, poller, queue and operatio
|
|
|
232
284
|
type: architecture
|
|
233
285
|
status: active
|
|
234
286
|
created: 2026-08-24
|
|
235
|
-
updated: 2026-
|
|
287
|
+
updated: 2026-10-04 13:28 +07:00
|
|
236
288
|
---
|
|
237
289
|
|
|
238
290
|
# Application migration recipes
|
|
@@ -266,6 +318,131 @@ The application still owns the client, ORM configuration, transactions,
|
|
|
266
318
|
migrations and reconnect policy. The executable recipe deliberately fails after
|
|
267
319
|
allocation and proves one rollback close.
|
|
268
320
|
|
|
321
|
+
## Stateful clients, including MTProto
|
|
322
|
+
|
|
323
|
+
An application-owned client is an ordinary managed resource. Keep SDK creation,
|
|
324
|
+
credentials, authorization and reconnect policy in the application; no Telegram
|
|
325
|
+
client SDK is required by Stitchkit. Create a fresh client inside each `start`,
|
|
326
|
+
publish it after connection/readiness succeeds, and share one memoized destroy
|
|
327
|
+
promise between partial-start cleanup, `close` and `force`:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
let generation: { destroy(): Promise<void> } | undefined
|
|
331
|
+
|
|
332
|
+
const client = defineManagedResource({
|
|
333
|
+
id: 'client',
|
|
334
|
+
async start({ signal }) {
|
|
335
|
+
const connection = createClient() // Your SDK and configuration.
|
|
336
|
+
let destruction: Promise<void> | undefined
|
|
337
|
+
const destroy = () => destruction ??= Promise.resolve()
|
|
338
|
+
.then(() => connection.destroy())
|
|
339
|
+
generation = { destroy }
|
|
340
|
+
let ready = false
|
|
341
|
+
let startFailure: unknown
|
|
342
|
+
let startFailed = false
|
|
343
|
+
let cleanupFailure: unknown
|
|
344
|
+
let cleanupFailed = false
|
|
345
|
+
try {
|
|
346
|
+
await connection.connect(signal)
|
|
347
|
+
await connection.assertReady()
|
|
348
|
+
signal.throwIfAborted()
|
|
349
|
+
ready = true
|
|
350
|
+
} catch (error) {
|
|
351
|
+
startFailure = error
|
|
352
|
+
startFailed = true
|
|
353
|
+
} finally {
|
|
354
|
+
if (!ready) {
|
|
355
|
+
try { await destroy() }
|
|
356
|
+
catch (cleanup) {
|
|
357
|
+
cleanupFailure = cleanup
|
|
358
|
+
cleanupFailed = true
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (startFailed) {
|
|
363
|
+
if (cleanupFailed) throw new AggregateError([startFailure, cleanupFailure],
|
|
364
|
+
'Client startup and cleanup failed', { cause: startFailure })
|
|
365
|
+
throw startFailure
|
|
366
|
+
}
|
|
367
|
+
return { value: connection }
|
|
368
|
+
},
|
|
369
|
+
close: () => generation?.destroy(),
|
|
370
|
+
force: () => generation?.destroy(),
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
const api = defineManagedResource({
|
|
374
|
+
id: 'api',
|
|
375
|
+
dependsOn: [client],
|
|
376
|
+
start(context) {
|
|
377
|
+
const connection = context.use(client)
|
|
378
|
+
return { value: async () => {
|
|
379
|
+
const lease = context.admission.acquire()
|
|
380
|
+
if (!lease) throw new ApplicationAdmissionError()
|
|
381
|
+
try { return await connection.invoke() }
|
|
382
|
+
finally { lease.release() }
|
|
383
|
+
} }
|
|
384
|
+
},
|
|
385
|
+
})
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`createClient` supplies a client whose `destroy` safely cleans partial connection
|
|
389
|
+
setup; its adapter must honor cancellation or otherwise reconcile late SDK effects.
|
|
390
|
+
The destroy cache belongs to one generation and is reset by the next `start`.
|
|
391
|
+
Successful startup keeps the connection alive. Dependants start after readiness,
|
|
392
|
+
requests acquire admission before invoking the client, and dependent HTTP resources
|
|
393
|
+
close before the client. A subtree restart builds a new client and new dependants.
|
|
394
|
+
|
|
395
|
+
The packed-consumer fixture
|
|
396
|
+
[`managed-client-composition.mjs`](../../packages/core/scripts/consumer-lane/fixtures/minimal/src/managed-client-composition.mjs)
|
|
397
|
+
proves readiness/admission, partial startup, close order, forced cleanup and restart
|
|
398
|
+
with a fake client on Bun and Node. It does not claim a live provider acceptance test.
|
|
399
|
+
|
|
400
|
+
## Custom StateStore transaction guards
|
|
401
|
+
|
|
402
|
+
Transitions now receive `(current, context)`. Keep ordinary callbacks that only use
|
|
403
|
+
`current`; update custom store implementations to pass a real protected context.
|
|
404
|
+
For an in-memory test store, queue transactions and invalidate the context when
|
|
405
|
+
the transaction finishes:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
function memoryStore<T>(): StateStore<T> {
|
|
409
|
+
let state: T | null = null
|
|
410
|
+
let tail: Promise<unknown> = Promise.resolve()
|
|
411
|
+
return {
|
|
412
|
+
read: async () => state,
|
|
413
|
+
update(transition) {
|
|
414
|
+
const work = tail.then(async () => {
|
|
415
|
+
let active = true
|
|
416
|
+
const context: StateStoreUpdateContext = {
|
|
417
|
+
async assertHeld() {
|
|
418
|
+
if (!active) throw new Error('Transaction is no longer active')
|
|
419
|
+
},
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
const next = await transition(state, context)
|
|
423
|
+
await context.assertHeld()
|
|
424
|
+
state = next.state
|
|
425
|
+
return next.result
|
|
426
|
+
} finally { active = false }
|
|
427
|
+
})
|
|
428
|
+
tail = work.catch(() => undefined)
|
|
429
|
+
return work
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
This recipe is process-local and has no restart durability. A database store must
|
|
436
|
+
guard its real transaction/generation and commit atomically; an escaped or lost
|
|
437
|
+
guard must refuse. Filesystem mutations made inside inbox transactions call
|
|
438
|
+
`assertHeld` immediately before publication or cleanup.
|
|
439
|
+
|
|
440
|
+
For `createFileStateStore`, remove `staleLockMs` and `retryMs` from configuration.
|
|
441
|
+
`lockTimeoutMs` remains the acquisition budget. Timestamp age no longer revokes
|
|
442
|
+
a live or unknown writer. Stop and verify old writers before recovering a legacy
|
|
443
|
+
pid-only lock; do not invent owner metadata. Historical temporary files are no
|
|
444
|
+
longer deleted by age. See the [durable intake contract](application-kernel.md#durable-process-facts-and-owner-notifications).
|
|
445
|
+
|
|
269
446
|
## Long-running poller
|
|
270
447
|
|
|
271
448
|
Return separate readiness and completion promises. Readiness says dependants may
|
package/llms/application.3.txt
CHANGED
|
@@ -36,7 +36,9 @@ cutovers are covered by the executable
|
|
|
36
36
|
| `NotificationOutboxItem` / `NotificationOutboxReceipt` / `NotificationOutboxState` / `NotificationSend` / `NotificationFailureClassification` / `DroppedNotification` / `EnqueueNotification` | _type_ | versioned queue records, stable send identity and delivery decisions |
|
|
37
37
|
| `notificationOutboxStateSchema` | function | strict persisted-state schema over an application payload schema |
|
|
38
38
|
| `notificationOutboxResource` / `NotificationOutboxResource` / `NotificationOutboxResourceConfig` | function / _type_ | thin managed-resource scheduler over an outbox handle |
|
|
39
|
-
| `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | managed
|
|
39
|
+
| `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 |
|
|
40
|
+
| `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 |
|
|
41
|
+
| `StateStoreUpdateContext` | _type_ | real active protected transaction: `assertHeld()` refuses after ownership loss or completion; custom adapters supply this context to their transition |
|
|
40
42
|
| `DirectoryInboxStateSchema` / `DirectoryInboxState` / `DirectoryInboxRejection` / `DirectoryInboxRejectionReasonSchema` / `DirectoryInboxRejectionReason` | schema / _type_ | the inbox's claims, receipts and rejections (`invalid`, `too-large`, `attempt-limit`) |
|
|
41
43
|
| `ApplicationHandle.restart` | method | replace one resource and everything that depends on it, leaving the rest of the graph running and the process epoch unchanged |
|
|
42
44
|
| `ApplicationRestartInputSchema` / `ApplicationRestartInput` | schema / _type_ | the resource to replace, by id, and optionally `gracePeriodMs` / `forceTimeoutMs` for this restart — the application's own shutdown budget otherwise |
|
package/llms/application.txt
CHANGED
package/llms/cli.2.txt
CHANGED
|
@@ -5,7 +5,146 @@
|
|
|
5
5
|
|
|
6
6
|
Parts of this slice:
|
|
7
7
|
- `llms/cli.txt`: Guide: CLI (docs/guide/cli.md)
|
|
8
|
-
- `llms/cli.2.txt`: API reference: `stitchkit/cli` (docs/api/reference.md)
|
|
8
|
+
- `llms/cli.2.txt`: Guide: CLI (docs/guide/cli.md) · API reference: `stitchkit/cli` (docs/api/reference.md)
|
|
9
|
+
|
|
10
|
+
==============================================================================
|
|
11
|
+
# Guide: CLI (docs/guide/cli.md) (continued)
|
|
12
|
+
==============================================================================
|
|
13
|
+
|
|
14
|
+
## Commands discovered from a running server
|
|
15
|
+
|
|
16
|
+
A CLI compiled from contracts carries the surface of the build it was compiled
|
|
17
|
+
from. One built from discovery carries the surface the server has *right now* —
|
|
18
|
+
which matters, because a long-lived MCP client freezes schemas at connect time
|
|
19
|
+
and then refuses the server's own newer fields:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const discovered = await mountConnections([
|
|
23
|
+
defineMcpClientConnection({
|
|
24
|
+
name: 'api',
|
|
25
|
+
transport: { url },
|
|
26
|
+
token: () => key,
|
|
27
|
+
transports: ['CLI'], // the opt-in: this server's tools are commands
|
|
28
|
+
limits: {
|
|
29
|
+
discovery: { timeoutMs: 30_000, maxResponseBytes: 4 * 1024 * 1024 },
|
|
30
|
+
call: { timeoutMs: 120_000 },
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
])
|
|
34
|
+
await createCli({ name: 'myapp', version, runtimeTools: discovered, commands: [...] })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`transports` is where the opt-in belongs: a whole server becomes a set of
|
|
38
|
+
commands, and a connection without it still contributes nothing to the CLI —
|
|
39
|
+
exposure stays explicit, as it is everywhere else in the framework.
|
|
40
|
+
|
|
41
|
+
One unconvertible schema no longer takes the connection down with it. The tool
|
|
42
|
+
is skipped and **named** (`onSkippedTool`, or a stderr line by default), so a
|
|
43
|
+
surface of two hundred tools is not lost to one.
|
|
44
|
+
|
|
45
|
+
MCP connection limits follow the operation phase. `initialize`, the initialized
|
|
46
|
+
notification, `tools/list` and legacy endpoint discovery use `limits.discovery`;
|
|
47
|
+
`tools/call` uses `limits.call`. A mounted call's handshake still uses discovery
|
|
48
|
+
limits. Each logical operation has one deadline, including initial transport
|
|
49
|
+
negotiation and response-body reading. Legacy endpoint readiness has a discovery
|
|
50
|
+
deadline; the lifetime of an established SSE stream does not.
|
|
51
|
+
|
|
52
|
+
Omitted phase fields inherit the existing connection-level `timeoutMs` and
|
|
53
|
+
`maxResponseBytes`, whose defaults are 30 seconds and 1 MiB. Values must be positive
|
|
54
|
+
safe integers; `timeoutMs` must also fit the runtime timer range. The byte ceiling
|
|
55
|
+
counts raw UTF-8 body bytes, including SSE framing, regardless of `Content-Length`.
|
|
56
|
+
Pending legacy calls also count comments and wrong-id frames. `budget.maxTools`
|
|
57
|
+
and `budget.maxSchemaBytes` remain separate mount limits.
|
|
58
|
+
|
|
59
|
+
Only the initial `initialize` can negotiate HTTP-to-legacy fallback on 400, 404
|
|
60
|
+
or 405. A failed `tools/call` is not replayed. Caller cancellation preserves its
|
|
61
|
+
original reason for in-process observers.
|
|
62
|
+
|
|
63
|
+
Expected local failures produce safe tool errors: `CONNECTION_TIMEOUT` (exit 7),
|
|
64
|
+
`CONNECTION_RESPONSE_TOO_LARGE` (exit 1), `UNAUTHORIZED` (exit 2), `FORBIDDEN`
|
|
65
|
+
(exit 3) or `CONNECTION_REQUEST_FAILED` (exit 1). Deadline and response-limit
|
|
66
|
+
failures include fixed `operation`/`phase`, the applicable bound and measured
|
|
67
|
+
`observedReadBytes`. The public `ConnectionTimeoutError` and
|
|
68
|
+
`ConnectionResponseTooLargeError` classes carry the same diagnostics. Raw URL,
|
|
69
|
+
credential, body, identity and stack stay with the original in-process cause in
|
|
70
|
+
`onToolError` / `afterToolCall`; tool errors produce no process console dump.
|
|
71
|
+
Unknown throws remain `INTERNAL_SERVER_ERROR`. A timeout is not proof that a
|
|
72
|
+
write did not happen: verify the remote outcome before repeating an operation.
|
|
73
|
+
|
|
74
|
+
**A remote refusal keeps its code.** A failed `tools/call` used to become a
|
|
75
|
+
one-sentence `Error` with the result discarded, which cost three things at once:
|
|
76
|
+
the code (so `exitCodes` had nothing to map and every remote failure exited `1`),
|
|
77
|
+
the message the operator needed, and the error's own class — a plain `Error` is
|
|
78
|
+
an *unexpected* error to the runner, so it printed a code frame of the framework
|
|
79
|
+
bundle before the JSON failure and was then scrubbed to `INTERNAL_SERVER_ERROR`.
|
|
80
|
+
A structured `{ error, details }` body is now relayed as the contract error it
|
|
81
|
+
is, on every transport; anything else fails as `UPSTREAM_TOOL_ERROR` carrying
|
|
82
|
+
what the server did send. Not `INTERNAL_SERVER_ERROR`, because nothing of ours
|
|
83
|
+
broke.
|
|
84
|
+
|
|
85
|
+
**On the CLI a discovered command prints the answer, not the envelope.**
|
|
86
|
+
`tools/call` returns `{ content: [...], structuredContent? }`, and an agent mount
|
|
87
|
+
needs exactly that — the parts are what a model is shown. The CLI is different in
|
|
88
|
+
kind, because the handler's value is what gets printed, piped and aggregated:
|
|
89
|
+
handed the envelope, `--count-by status` groups the *content parts* and answers
|
|
90
|
+
`no record carries the field "status" — available: text, type`. So the CLI
|
|
91
|
+
transport unwraps, and only it: `structuredContent` when the server sent one, a
|
|
92
|
+
lone text part when it parses as JSON, its text when it does not, and `null` when
|
|
93
|
+
there are no parts at all — a tool without an output contract answers that way,
|
|
94
|
+
and the `_meta` beside an empty `content` is protocol metadata, never the answer.
|
|
95
|
+
Several parts, an image or audio pass through whole — picking one of many would
|
|
96
|
+
be inventing an answer.
|
|
97
|
+
|
|
98
|
+
## Auth parity
|
|
99
|
+
|
|
100
|
+
A scoped command is guarded by the same `createAuthHook` your HTTP server uses —
|
|
101
|
+
pass it as `lifecycle`, and inject the identity through `context` so
|
|
102
|
+
`resolveFromContext` can read it:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const authHook = createAuthHook({ /* resolve, resolveFromContext, rules */ })
|
|
106
|
+
|
|
107
|
+
await createCli({
|
|
108
|
+
name: 'myapp',
|
|
109
|
+
version: '1.0.0',
|
|
110
|
+
resolveAuth: () => resolveIdentityFromToken(process.env.MYAPP_TOKEN),
|
|
111
|
+
context: (identity) => ({ user: identity }), // resolveFromContext reads this
|
|
112
|
+
lifecycle: { beforeHandle: authHook }, // same policy; HTTP wires it as authorize
|
|
113
|
+
services,
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Without `lifecycle`, a scoped command runs **unguarded** — the scope check lives
|
|
118
|
+
entirely inside the `createAuthHook` result, so with no hook wired in there is
|
|
119
|
+
nothing to enforce a method's `scope`. This matches the MCP / agent surfaces
|
|
120
|
+
exactly ([ADR 0014](../decisions/0014-tool-http-parity.md)): on every tool
|
|
121
|
+
transport the auth gate is opt-in, so a contract with scoped methods **must** be
|
|
122
|
+
given a `lifecycle` (and `context` identity) to be protected. The gate only
|
|
123
|
+
*fails closed* once the hook **is** present but `resolveFromContext` is missing —
|
|
124
|
+
then a scoped call has no identity and is rejected.
|
|
125
|
+
|
|
126
|
+
## Typed context
|
|
127
|
+
|
|
128
|
+
Use `createToolkit<AppContext>()` to type the injected `context` against your
|
|
129
|
+
app's context shape — the tool-side mirror of `createImplement`
|
|
130
|
+
([ADR 0017](../decisions/0017-typed-tool-context.md)):
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const tools = createToolkit<{ user: User }>()
|
|
134
|
+
await tools.createCli({
|
|
135
|
+
name: 'myapp',
|
|
136
|
+
version: '1.0.0',
|
|
137
|
+
services,
|
|
138
|
+
context: (identity) => ({ user: identity }), // checked against { user: User }
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Remaining boundary
|
|
143
|
+
|
|
144
|
+
File-upload (`multipart`) contract endpoints remain CLI-invisible, the same as
|
|
145
|
+
on MCP/Agent: their wire body is not a JSON tool form. Model file-oriented
|
|
146
|
+
application behavior as a managed pathless command, or binary-only behavior as
|
|
147
|
+
a native command. Streaming (SSE) output is not yet piped to stdout.
|
|
9
148
|
|
|
10
149
|
==============================================================================
|
|
11
150
|
# API reference: `stitchkit/cli` (docs/api/reference.md)
|
|
@@ -33,6 +172,7 @@ SDK nor the `ai` peer.
|
|
|
33
172
|
| `checkCliUpdate` | function | bounded, interval-limited, never-throwing check for a newer published build |
|
|
34
173
|
| `applyCliUpdate` | function | download, verify the decompressed digest and replace the binary by rename |
|
|
35
174
|
| `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 |
|
|
175
|
+
| `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 |
|
|
36
176
|
| `rollbackCliUpdate` / `CliRollbackConfig` / `RolledBackCliUpdate` | function / _types_ | put the kept previous build back, digest-checked first and written by the same atomic rename the update uses |
|
|
37
177
|
| `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 |
|
|
38
178
|
| `cliSignatureAccepted` / `CliSignatureVerdict` | function / _type_ | `valid` \| `unenforced` \| `missing` \| `unknown-key` \| `invalid`; `unenforced` keeps an unpinned build updating while making the absence of a check visible |
|
|
@@ -64,6 +204,9 @@ SDK nor the `ai` peer.
|
|
|
64
204
|
| `CliConfig` | _type_ | config for `createCli`; `defaultCommand`, `globalOptions`, `optionAliases` and `positionals` define the shared command presentation policy |
|
|
65
205
|
| `CliPresentationPolicyConfig` | _type_ | shared command presentation-policy subset of `CliConfig` |
|
|
66
206
|
| `CliSurfaceSource` | _type_ | static service/runtime array or identity-dependent factory |
|
|
207
|
+
| `RuntimeToolExecution` | _type_ | neutral heterogeneous runtime-tool registration; handlers execute through the schema-validating managed runner |
|
|
208
|
+
| `RuntimeToolExecutionWithOutput` | _type_ | strict neutral managed definition with schema-derived handler input/output; construct before adding to CLI `runtimeTools` |
|
|
209
|
+
| `RuntimeToolDefinitionWithoutOutput` | _type_ | strict neutral managed void definition without an output contract or transport presenters |
|
|
67
210
|
| `CliCommandDefinition` | _type_ | native command definition union |
|
|
68
211
|
| `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
|
|
69
212
|
| `CliCommandDefinitionWithOutput` | _type_ | native command with validated declared output and typed optional `present` / successful `exitCode` callbacks |
|