stitchkit 0.106.1 → 0.108.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.
- package/CHANGELOG.md +151 -0
- package/README.md +2 -0
- package/darwin-native.cjs +17 -4
- package/dist/agent-runtime/deferred-tool-types.d.ts +6 -27
- package/dist/agent-runtime/deferred-tool-types.d.ts.map +1 -1
- package/dist/browser/stream.d.ts +38 -12
- package/dist/browser/stream.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/browser.js +2 -3
- package/dist/entrypoints/agent-runtime/coding-tools.js +6 -8
- package/dist/entrypoints/agent-runtime/harness.js +7 -11
- package/dist/entrypoints/agent-runtime/realtime.js +2 -3
- package/dist/entrypoints/agent-runtime/sandbox.js +6 -8
- package/dist/entrypoints/agent-runtime.d.ts +2 -1
- package/dist/entrypoints/agent-runtime.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime.js +20 -23
- package/dist/entrypoints/application/diagnostic-journal.js +3 -3
- package/dist/entrypoints/application/directory-inbox.js +8 -9
- package/dist/entrypoints/application/grammy.js +3 -3
- package/dist/entrypoints/application.js +7 -8
- package/dist/entrypoints/bin/upgrade-cli.d.ts +32 -0
- package/dist/entrypoints/bin/upgrade-cli.d.ts.map +1 -1
- package/dist/entrypoints/bin/upgrade-cli.js +451 -8
- package/dist/entrypoints/cli/publish.js +10 -11
- package/dist/entrypoints/cli.js +12 -16
- package/dist/entrypoints/contract.js +1 -1
- package/dist/entrypoints/files/packaging.d.ts +3 -1
- package/dist/entrypoints/files/packaging.d.ts.map +1 -1
- package/dist/entrypoints/files/packaging.js +171 -55
- package/dist/entrypoints/files.js +16 -16
- package/dist/entrypoints/index.d.ts +1 -1
- package/dist/entrypoints/index.d.ts.map +1 -1
- package/dist/entrypoints/index.js +47 -41
- package/dist/entrypoints/node.js +27 -29
- package/dist/entrypoints/observability.js +10 -12
- package/dist/entrypoints/process.js +2 -2
- package/dist/entrypoints/react.js +3 -4
- package/dist/entrypoints/remote.js +8 -9
- package/dist/entrypoints/server.js +48 -51
- package/dist/entrypoints/telegram/html.js +3 -3
- package/dist/entrypoints/telegram/screens.js +6 -6
- package/dist/entrypoints/telegram.d.ts +2 -0
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +344 -11
- package/dist/entrypoints/testing.d.ts +1 -0
- package/dist/entrypoints/testing.d.ts.map +1 -1
- package/dist/entrypoints/testing.js +138 -11
- package/dist/entrypoints/tools/connections.js +11 -11
- package/dist/entrypoints/tools/invoker.js +5 -9
- package/dist/entrypoints/tools/mcp.js +8 -14
- package/dist/entrypoints/tools.js +19 -25
- package/dist/entrypoints/tracking.js +3 -3
- package/dist/files/native-artifact.d.ts +14 -0
- package/dist/files/native-artifact.d.ts.map +1 -0
- package/dist/files/native-packaging-layout.d.ts +11 -2
- package/dist/files/native-packaging-layout.d.ts.map +1 -1
- package/dist/files/native-packaging-plugin.d.ts +53 -0
- package/dist/files/native-packaging-plugin.d.ts.map +1 -0
- package/dist/files/native-packaging.d.ts +44 -43
- package/dist/files/native-packaging.d.ts.map +1 -1
- package/dist/{index-v5ee8ath.js → index-1h2zv8rx.js} +10 -40
- package/dist/index-2kbysk9s.js +42 -0
- package/dist/{index-wbhdzxar.js → index-2s6fpehp.js} +5 -5
- package/dist/{index-ksfagmyz.js → index-4qh90tyw.js} +8 -8
- package/dist/{index-yvfk3y36.js → index-636mggad.js} +4 -3
- package/dist/{index-f199nfq5.js → index-6y9t2fwz.js} +7 -7
- package/dist/{index-w8etpm8j.js → index-9j2atjte.js} +1 -1
- package/dist/index-a5qr2rj6.js +26 -0
- package/dist/{index-azx12jgv.js → index-bh2ht9y5.js} +13 -7
- package/dist/{index-pz3y6b6g.js → index-cx2nh595.js} +3 -3
- package/dist/{index-2k91mewf.js → index-dm2fv45z.js} +9 -9
- package/dist/{index-9bskshe3.js → index-dwf0gymy.js} +10 -10
- package/dist/{index-qaqbdm7s.js → index-h7qms8f9.js} +8 -8
- package/dist/{index-drs41arz.js → index-ktjmgq9p.js} +1 -1
- package/dist/{index-916yh4e2.js → index-mep4sb48.js} +14 -14
- package/dist/{index-wdy68sh6.js → index-njs9gvrh.js} +17 -17
- package/dist/{index-1ptj98e0.js → index-nx49x0tw.js} +2 -2
- package/dist/{index-1pwwan01.js → index-p5kx5vqd.js} +214 -59
- package/dist/{index-sj28htvf.js → index-pvh3z03x.js} +1 -1
- package/dist/{index-r393tnfp.js → index-q6crhe1n.js} +4 -4
- package/dist/{index-dsnjzb62.js → index-t2bsd0hm.js} +1 -1
- package/dist/{index-4vsby8n8.js → index-w9mqgnz9.js} +1 -1
- package/dist/{index-z23rfxhj.js → index-xmgjtkvr.js} +1 -1
- package/dist/{index-e05cjgtf.js → index-xvj9h8yh.js} +28 -1
- package/dist/{index-gr5g3xfw.js → index-yz8x0hbx.js} +1 -1
- package/dist/{index-gxm9rc97.js → index-zrtrjkbf.js} +1 -1
- package/dist/internal/bounded-lines.d.ts +46 -2
- package/dist/internal/bounded-lines.d.ts.map +1 -1
- package/dist/internal/darwin-binding-error.d.ts +9 -1
- package/dist/internal/darwin-binding-error.d.ts.map +1 -1
- package/dist/internal/darwin-binding.d.ts.map +1 -1
- package/dist/internal/process-identity.d.ts.map +1 -1
- package/dist/internal/until-aborted.d.ts +9 -0
- package/dist/internal/until-aborted.d.ts.map +1 -0
- package/dist/internal/upgrade-affects.d.ts +58 -0
- package/dist/internal/upgrade-affects.d.ts.map +1 -0
- package/dist/internal/upgrade-plan.d.ts +20 -0
- package/dist/internal/upgrade-plan.d.ts.map +1 -1
- package/dist/internal/upgrade-usage.d.ts +61 -0
- package/dist/internal/upgrade-usage.d.ts.map +1 -0
- package/dist/telegram/bot-api.d.ts +10 -4
- package/dist/telegram/bot-api.d.ts.map +1 -1
- package/dist/telegram/broadcast.d.ts +2 -1
- package/dist/telegram/broadcast.d.ts.map +1 -1
- package/dist/telegram/not-dispatched.d.ts +26 -0
- package/dist/telegram/not-dispatched.d.ts.map +1 -0
- package/dist/telegram/operator-channel.d.ts +2 -1
- package/dist/telegram/operator-channel.d.ts.map +1 -1
- package/dist/telegram/send-failure.d.ts +4 -3
- package/dist/telegram/send-failure.d.ts.map +1 -1
- package/dist/telegram/transport-connect.d.ts +38 -0
- package/dist/telegram/transport-connect.d.ts.map +1 -0
- package/dist/telegram/transport.d.ts +43 -0
- package/dist/telegram/transport.d.ts.map +1 -0
- package/dist/telegram/webhook.d.ts +2 -1
- package/dist/telegram/webhook.d.ts.map +1 -1
- package/dist/testing/manual-clock.d.ts +82 -0
- package/dist/testing/manual-clock.d.ts.map +1 -0
- package/dist/tools/internal/surface-projector.d.ts +0 -7
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/runtime-tool-declaration.d.ts +11 -0
- package/dist/tools/runtime-tool-declaration.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +2 -3
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/llms/files-packaging.txt +13 -6
- package/llms/process.txt +71 -15
- package/llms/stitchkit.2.txt +211 -628
- package/llms/stitchkit.txt +635 -10
- package/llms/telegram.txt +36 -3
- package/llms/testing.txt +47 -0
- package/llms/{upgrading-0.96.0-0.106.0.txt → upgrading-0.100.0-0.108.0.txt} +112 -144
- package/llms/{upgrading-0.37.0-0.53.0.txt → upgrading-0.37.0-0.58.0.txt} +209 -3
- package/llms/{upgrading-0.55.0-0.68.0.txt → upgrading-0.59.0-0.71.0.txt} +149 -209
- package/llms/{upgrading-0.69.0-0.95.0.txt → upgrading-0.72.0-0.99.0.txt} +137 -149
- package/llms/upgrading.txt +36 -4
- package/llms-full.txt +360 -39
- package/llms.txt +10 -10
- package/package.json +2 -1
- package/llms/stitchkit.3.txt +0 -177
- package/dist/{index-cazts87s.js → index-07dcf2yn.js} +3 -3
- package/dist/{index-n1nszyk0.js → index-7jvwrjqa.js} +3 -3
- package/dist/{index-cmzvhhtd.js → index-85qve8mq.js} +3 -3
- package/dist/{index-rpzewktb.js → index-fynr93w4.js} +3 -3
- package/dist/{index-37asdw3a.js → index-k7qdkh3n.js} +3 -3
- package/dist/{index-x7w1z6pf.js → index-q4hjtyye.js} +4 -4
- package/dist/{index-7n68nmc6.js → index-y3nemndz.js} +3 -3
- package/dist/{index-12zsej7b.js → index-y70v24cw.js} +3 -3
- package/dist/{index-pzb63z66.js → index-ykdcnq1f.js} +3 -3
package/llms/telegram.txt
CHANGED
|
@@ -234,8 +234,8 @@ and pacing/progress intervals must fit native timers. The optional
|
|
|
234
234
|
when the signal aborts if a custom sleeper ignores it. A send already in flight
|
|
235
235
|
finishes and is recorded before stopping.
|
|
236
236
|
|
|
237
|
-
Without `classify`, known Bot API 429, unreachable-recipient, malformed-message
|
|
238
|
-
|
|
237
|
+
Without `classify`, known Bot API 429, unreachable-recipient, malformed-message,
|
|
238
|
+
server-error and not-dispatched behavior remains. Unknown/no-status failures now halt as
|
|
239
239
|
uncertain, rather than being automatically retried. Existing custom `send`
|
|
240
240
|
implementations that relied on those retries must supply an explicit classifier
|
|
241
241
|
for proven pre-effect failures. `report.halt` now carries
|
|
@@ -254,6 +254,36 @@ The installed
|
|
|
254
254
|
fixture checks injected rate limits, ambiguity/no resend, wait budgets and abort
|
|
255
255
|
on Bun and Node without a client SDK.
|
|
256
256
|
|
|
257
|
+
## A transport that knows whether a request left
|
|
258
|
+
|
|
259
|
+
`fetch` folds establishing the connection and waiting for the answer into one failure. From that
|
|
260
|
+
failure nobody can say whether Telegram saw the message, so the senders above treat every network
|
|
261
|
+
failure as unknown: a broadcast records the recipient `uncertain` and the operator channel drops
|
|
262
|
+
the message rather than risk a duplicate. On a route that sometimes fails to connect at all, that
|
|
263
|
+
loses messages that never left.
|
|
264
|
+
|
|
265
|
+
`createTelegramBotTransport()` connects first and writes the request only on an established
|
|
266
|
+
connection. Every failure before the first byte is written is `TelegramNotDispatchedError`
|
|
267
|
+
(`stage`: `lookup`, `connect` or `request`), and `classifyTelegramSendFailure` reads it as
|
|
268
|
+
`not-dispatched` — `retryable`, with `evidence: 'transport'`. A broadcast retries it as
|
|
269
|
+
`transient`; the operator channel sends it again. A failure after the request was written stays
|
|
270
|
+
an outcome nobody can know.
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
import { createTelegramBotTransport, telegramOperatorSender } from 'stitchkit/telegram'
|
|
274
|
+
|
|
275
|
+
const transport = createTelegramBotTransport()
|
|
276
|
+
const send = telegramOperatorSender({ token: env.BOT_TOKEN, fetch: transport })
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Pass it as `fetch` to `callTelegramBotApi`, `telegramBroadcastSender`, `telegramOperatorSender`
|
|
280
|
+
or the webhook calls. Addresses come in the system resolver's order with no family forced; the
|
|
281
|
+
next address starts 250 ms later (Happy Eyeballs), and a round with no connection is repeated
|
|
282
|
+
with new sockets inside `connectBudgetMs` (default 5 s, which also bounds the address lookup; each
|
|
283
|
+
round waits `connectAttemptMs`, default 2 s). The answer must arrive within `responseTimeoutMs` (default 30 s) and fit
|
|
284
|
+
`maxResponseBytes` (default 1 MiB). It speaks HTTP/1.1 over `https:`, and over `http:` for a
|
|
285
|
+
local Bot API server, and it uses no proxy: where the network needs one, keep `fetch`.
|
|
286
|
+
|
|
257
287
|
## Message markup
|
|
258
288
|
|
|
259
289
|
`stitchkit/telegram/html` is Telegram's HTML parse mode as a tree, with no peer
|
|
@@ -453,7 +483,10 @@ injected grammY bot. → ADR 0143, ADR 0201 — [guide](../guide/telegram.md)
|
|
|
453
483
|
| `TelegramInitDataRefusal` | _type_ | `missing-hash` / `signature-mismatch` / `malformed` / `expired` — an expired string is not a forged one |
|
|
454
484
|
| `classifyTelegramSendFailure` | function | name a refused Bot API send and separate "retry this send" from "stop addressing this recipient" |
|
|
455
485
|
| `TelegramSendFailure` | _type_ | reason, `status`, Telegram-stated `retryAfterSeconds`, `retryable`, `recipientUnreachable` and which evidence produced the answer |
|
|
456
|
-
| `
|
|
486
|
+
| `createTelegramBotTransport` / `TelegramBotTransportOptions` | function / _type_ | a `fetch` for the Bot API senders that connects before it writes, so a failure before the first byte is `TelegramNotDispatchedError`; connection attempt and budget, answer timeout and size, injectable resolver and socket factory — [guide](../guide/telegram.md#a-transport-that-knows-whether-a-request-left) |
|
|
487
|
+
| `TelegramFetch` / `TelegramBotTransportOpen` / `TelegramBotTransportResolve` / `TelegramBotTransportSocket` | _types_ | the `(url, init) => Promise<Response>` the senders take; the transport's socket factory, resolver and the structural socket it needs (a Node `net.Socket` or `tls.TLSSocket` fits) |
|
|
488
|
+
| `TelegramNotDispatchedError` / `TelegramNotDispatchedStage` | class / _type_ | a request that provably never left (`lookup`, `connect` or `request`), so repeating it cannot duplicate a message |
|
|
489
|
+
| `TelegramSendFailureReason` / `TELEGRAM_SEND_FAILURE_REASONS` | _type_ / constant | `blocked-by-user` / `user-deactivated` / `chat-not-found` / `not-started` / `rate-limited` / `message-invalid` / `server-error` / `not-dispatched` / `unknown`, and the same as a list; a `Record<TelegramSendFailureReason, …>` over them fails to compile when a reason is added |
|
|
457
490
|
| `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 |
|
|
458
491
|
| `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` |
|
|
459
492
|
| `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 |
|
package/llms/testing.txt
CHANGED
|
@@ -223,6 +223,50 @@ The suite in `packages/core/tests` is the working reference for testing each
|
|
|
223
223
|
piece. Its size changes with the public surface, so the guide does not pin a
|
|
224
224
|
count that can drift independently from the test runner.
|
|
225
225
|
|
|
226
|
+
### Manual time for schedules and retries
|
|
227
|
+
|
|
228
|
+
`createManualClock()` from `stitchkit/testing` stands time still until the test moves it. It is
|
|
229
|
+
the `clock` of `createManagedSchedule`, a durable sleep, a revision signal or bounded admission,
|
|
230
|
+
and the `schedule` a retrier of your own takes:
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { createManagedSchedule } from 'stitchkit/application'
|
|
234
|
+
import { createManualClock } from 'stitchkit/testing'
|
|
235
|
+
|
|
236
|
+
const clock = createManualClock()
|
|
237
|
+
const schedule = createManagedSchedule({ id: 'sweep', everyMs: 1_000, clock, run })
|
|
238
|
+
// …start and activate it…
|
|
239
|
+
await clock.advance(3_500) // runs at 1 s, 2 s and 3 s of manual time; the test takes milliseconds
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Only one `advance` (or `until`) runs at a time; a second one started before the first settled is
|
|
243
|
+
refused, because two interleaved moves would run time backwards.
|
|
244
|
+
|
|
245
|
+
Due timers fire in deadline order, ties in scheduling order. After each firing the clock yields
|
|
246
|
+
real event-loop turns, not just microtasks: a continuation that arms the next retry after an
|
|
247
|
+
already-readable socket or another macrotask has armed it before the clock moves on, so
|
|
248
|
+
`advance(10_000)` does not skip a retry armed at the fifth second. Real work of unknown length —
|
|
249
|
+
a request to a fake server, a hash in the thread pool — outruns any number of turns; the test
|
|
250
|
+
wraps the dependency it injects in a `hold`:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
const request = async (body) => {
|
|
254
|
+
const held = clock.hold('upload to the fake server')
|
|
255
|
+
try {
|
|
256
|
+
return await fakeServer.send(body)
|
|
257
|
+
} finally {
|
|
258
|
+
held.release()
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Time does not move while a hold is open; past `holdLimitMs` of real time (default 5 s)
|
|
264
|
+
`advance` fails with `ManualClockError` `MANUAL_CLOCK_HOLD_TIMEOUT`, naming every open hold. A
|
|
265
|
+
held operation that waits for the clock itself — a request timing out on a deadline — `park`s
|
|
266
|
+
its hold so the deadline can come. `until(done)` moves from deadline to deadline until `done()`
|
|
267
|
+
holds and fails with `MANUAL_CLOCK_LIMIT` past `limitMs` of manual time; `pending()` counts the
|
|
268
|
+
timers still waiting, so a timer leaked past `close()` shows as a number, not as a hang.
|
|
269
|
+
|
|
226
270
|
### Managed-resource conformance
|
|
227
271
|
|
|
228
272
|
Consumer-owned resource adapters can run the framework's deterministic
|
|
@@ -617,6 +661,9 @@ handler pipeline without opening a TCP port.
|
|
|
617
661
|
|--------|------|---------|
|
|
618
662
|
| `createHandlerTestClient` | function | one contract client backed by an in-process `FetchHandler` |
|
|
619
663
|
| `createHandlerTestClients` | function | exact contract-registry batch form |
|
|
664
|
+
| `createManualClock` / `ManualClock` | function / _type_ | manual time for schedules, retries and deadlines: `now`, `wallNow`, `schedule`, `advance(ms)` firing due timers in order with real event-loop yields between them, `until`, `pending` and `hold`; a `ManagedScheduleClock`, `DurabilityClock`, `RevisionSignalClock` and `BoundedAdmissionClock` — [guide](../guide/testing-and-deployment.md#manual-time-for-schedules-and-retries) |
|
|
665
|
+
| `ManualClockOptions` / `ManualClockUntilOptions` / `ManualClockTimer` / `ManualClockHold` | _types_ | start and wall instants, event-loop turns per firing and the real-time hold limit; `until` step and manual limit; a cancellable timer; a hold that is released or parked |
|
|
666
|
+
| `ManualClockError` / `ManualClockErrorCode` | class / _type_ | `MANUAL_CLOCK_HOLD_TIMEOUT` naming the open holds, or `MANUAL_CLOCK_LIMIT` when `until` runs out |
|
|
620
667
|
| `runManagedResourceConformance` | function | run the canonical deterministic lifecycle matrix against a fresh consumer-owned `ManagedResource` fixture; resolves `void` or throws `ManagedResourceConformanceError` with a stable scenario ID and normalized trace |
|
|
621
668
|
| `ManagedResourceConformanceScenarioIdSchema` / `ManagedResourceConformanceScenarioId` | schema / _type_ | stable clean, rollback, readiness/completion, activation, shutdown-race and forced-cleanup scenario vocabulary |
|
|
622
669
|
| `ManagedResourceConformanceScenarioSchema` / `ManagedResourceConformanceScenario` | schema / _type_ | discriminated scenario record including whether the controlled resource is required |
|
|
@@ -1,14 +1,102 @@
|
|
|
1
|
-
# Upgrading 0.
|
|
1
|
+
# Upgrading 0.100.0 → 0.108.0 — stitchkit docs slice
|
|
2
2
|
|
|
3
3
|
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
4
|
> `llms.txt` in this package.
|
|
5
5
|
|
|
6
|
-
Contains: Guide: Upgrading, 0.
|
|
6
|
+
Contains: Guide: Upgrading, 0.100.0 → 0.108.0 (docs/guide/upgrading.md)
|
|
7
7
|
|
|
8
8
|
==============================================================================
|
|
9
|
-
# Guide: Upgrading, 0.
|
|
9
|
+
# Guide: Upgrading, 0.100.0 → 0.108.0 (docs/guide/upgrading.md)
|
|
10
10
|
==============================================================================
|
|
11
11
|
|
|
12
|
+
## Released migration: 0.108.0
|
|
13
|
+
|
|
14
|
+
### a request that never left is its own send outcome
|
|
15
|
+
|
|
16
|
+
**Who must act:** code with an exhaustive `switch` or `Record` over `TelegramSendFailureReason` or
|
|
17
|
+
over `TelegramSendFailure['evidence']` (`stitchkit/telegram`). Code that reads `retryable` and
|
|
18
|
+
`recipientUnreachable` needs no change.
|
|
19
|
+
|
|
20
|
+
`classifyTelegramSendFailure` now returns `reason: 'not-dispatched'` with `evidence: 'transport'`
|
|
21
|
+
for a `TelegramNotDispatchedError`: the request never left, so it is `retryable` and repeating it
|
|
22
|
+
cannot duplicate a message
|
|
23
|
+
([ADR 0253](../decisions/0253-a-telegram-request-that-never-left-is-safe-to-repeat.md)). Only
|
|
24
|
+
`createTelegramBotTransport` throws that error; with the default `fetch` nothing changes at run time.
|
|
25
|
+
|
|
26
|
+
1. Add the reason and the evidence to every exhaustive mapping:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// before
|
|
30
|
+
const code: Record<TelegramSendFailureReason, string> = { /* … */ unknown: 'E_UNKNOWN' }
|
|
31
|
+
// after
|
|
32
|
+
const code: Record<TelegramSendFailureReason, string> = {
|
|
33
|
+
/* … */ 'not-dispatched': 'E_NOT_SENT', unknown: 'E_UNKNOWN',
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Search pattern: `TelegramSendFailureReason`, `TELEGRAM_SEND_FAILURE_REASONS` and `evidence ===`.
|
|
38
|
+
2. Type-check: a `Record` or an exhaustive `switch` without the new members fails to compile.
|
|
39
|
+
3. To use the distinction, pass `fetch: createTelegramBotTransport()` to the senders that need it
|
|
40
|
+
(see the [Telegram guide](telegram.md#a-transport-that-knows-whether-a-request-left)).
|
|
41
|
+
|
|
42
|
+
### a packaging plugin packages only its own installation
|
|
43
|
+
|
|
44
|
+
**Who must act:** builds that call `createNativePackaging` (`stitchkit/files/packaging`) from a
|
|
45
|
+
`stitchkit` installation other than the one the bundled entry imports — typically a build tool that
|
|
46
|
+
carries its own copy. Builds whose entry and plugin come from one installation are unchanged.
|
|
47
|
+
|
|
48
|
+
Such a build used to succeed and ship the other installation's default Darwin loader, which refuses
|
|
49
|
+
the addon on macOS at run time. It now fails, naming both versions and roots
|
|
50
|
+
([ADR 0254](../decisions/0254-a-packaging-plugin-owns-one-loader-and-names-what-it-finds.md)).
|
|
51
|
+
|
|
52
|
+
1. Create the plugin from the `stitchkit` the entry resolves, not from the build tool's copy:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// before: the build tool's own stitchkit
|
|
56
|
+
import { createNativePackaging } from 'stitchkit/files/packaging'
|
|
57
|
+
// after: resolved from the project that owns the entry
|
|
58
|
+
const { createNativePackaging } = await import(
|
|
59
|
+
Bun.resolveSync('stitchkit/files/packaging', projectRoot)
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Search pattern: `createNativePackaging` in build tooling that is installed apart from the project.
|
|
64
|
+
2. Check the artifact with `inspectNativeArtifact`: a correct build reads `packaged`. An artifact
|
|
65
|
+
built by 0.107.0 or earlier carries no loader marker and reads `no-loader` whatever it holds, so
|
|
66
|
+
rebuild before relying on the check; a test of `=== 'unpackaged'` alone passes such an artifact.
|
|
67
|
+
|
|
68
|
+
## Released migration: 0.107.0
|
|
69
|
+
|
|
70
|
+
### embedded native packaging names no paths
|
|
71
|
+
|
|
72
|
+
**Who must act:** builds that call `createNativePackaging` (`stitchkit/files/packaging`) with
|
|
73
|
+
`delivery: 'embedded'`. Companion delivery is unchanged.
|
|
74
|
+
|
|
75
|
+
A standalone executable carries the Darwin addon inside itself, so the embedded form takes only
|
|
76
|
+
`platform`, `architecture` and `delivery`. `entryPath` and `assetPath` were required before but
|
|
77
|
+
changed nothing in the executable; passing them now is a type error and a schema refusal. The
|
|
78
|
+
ready result names the embedded addon by `NativePackagingEmbeddedAsset` (`bytes`, `size`,
|
|
79
|
+
`sha256`), without `outputPath`, since nothing is written beside the executable.
|
|
80
|
+
|
|
81
|
+
1. Drop the two paths from the embedded call:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// before
|
|
85
|
+
createNativePackaging({
|
|
86
|
+
platform: 'darwin', architecture: 'arm64', delivery: 'embedded',
|
|
87
|
+
entryPath: 'cli.js', assetPath: 'native/darwin-arm64.node',
|
|
88
|
+
})
|
|
89
|
+
// after
|
|
90
|
+
createNativePackaging({ platform: 'darwin', architecture: 'arm64', delivery: 'embedded' })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Search pattern: `delivery: 'embedded'`.
|
|
94
|
+
2. Code that read `asset.outputPath` from an embedded result reads `bytes`, `size` and `sha256`
|
|
95
|
+
only; an annotation `NativePackagingOptions` / `NativePackagingResult` on an embedded call
|
|
96
|
+
becomes `NativePackagingEmbeddedOptions` / `NativePackagingEmbeddedResult`.
|
|
97
|
+
3. Rebuild every artifact made without the plugin by 0.105.0–0.106.1: it carries the build
|
|
98
|
+
machine's package path (see [Darwin native assets in JS distributions](#darwin-native-assets-in-js-distributions)).
|
|
99
|
+
|
|
12
100
|
## Released migration: 0.106.0
|
|
13
101
|
|
|
14
102
|
### a stopped leader settles as stopped
|
|
@@ -609,13 +697,27 @@ Custom output layout uses the evolving build-only packaging contract in the
|
|
|
609
697
|
Three deliveries have different rules: an ordinary npm install retains the package's
|
|
610
698
|
native assets; a JS distribution must ship its complete output graph; a Bun compiled
|
|
611
699
|
executable embeds the matching addon and may still use `--outfile`. In 0.103.13–0.104.2 a plain
|
|
612
|
-
`bun build` carries the addon without a plugin
|
|
613
|
-
uses `createNativePackaging` (`delivery: 'companion'` for JS, `'embedded'` for a
|
|
614
|
-
executable).
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
700
|
+
`bun build` carries the addon without a plugin. Later releases load it from a bundle only when
|
|
701
|
+
the build uses `createNativePackaging` (`delivery: 'companion'` for JS, `'embedded'` for a
|
|
702
|
+
compiled executable). Without the plugin, Bun may still copy the addon's bytes into a compiled
|
|
703
|
+
executable, but the loader never uses them, so their presence proves nothing.
|
|
704
|
+
|
|
705
|
+
What an unpackaged bundle does depends on the release:
|
|
706
|
+
|
|
707
|
+
- **0.105.0–0.106.1:** the bundle carries the absolute path of the package on the build machine,
|
|
708
|
+
because the loader read `__dirname`. It looks for the addon there, so it works only on that
|
|
709
|
+
machine, and the path itself leaks into a distributed artifact. Rebuild with a later release.
|
|
710
|
+
- **0.107.0 and later:** no build path is in the artifact, and the first call that needs the
|
|
711
|
+
addon reports the Darwin backend `unavailable` with stage `packaging`.
|
|
712
|
+
|
|
713
|
+
Only macOS is affected, and only process identity (`observeProcessInstance`,
|
|
714
|
+
`probeProcessOwner`), exclusive locks and contained Agent file operations load the addon. The
|
|
715
|
+
managed file boundary, atomic writes and the chunk spool never do. The
|
|
716
|
+
[native IO guide](native-io.md#libraries-with-a-zod-only-runtime) is the canonical recipe and
|
|
717
|
+
shows how to check an artifact by its loader without running it on a Mac. Check the resulting
|
|
718
|
+
artifact offline outside the build tree and `node_modules`, including process identity,
|
|
719
|
+
live-owner lock refusal, dead-owner recovery and contained IO. A missing or corrupt addon
|
|
720
|
+
remains `unavailable`, never evidence that an owner died.
|
|
619
721
|
|
|
620
722
|
### CLI: repeated options are refused
|
|
621
723
|
|
|
@@ -798,137 +900,3 @@ recovery refuses rather than treating unknown ownership as death. From 0.100.1 a
|
|
|
798
900
|
Linux kernel without time namespaces (before 5.6) records the PID namespace alone
|
|
799
901
|
instead of null, so its locks recover like any other. Existing
|
|
800
902
|
`refuse` policy and the separately configured ownerless-file grace are unchanged.
|
|
801
|
-
|
|
802
|
-
## Released migration: 0.99.0
|
|
803
|
-
|
|
804
|
-
### structural alternatives in flattened tool schemas
|
|
805
|
-
|
|
806
|
-
**Who must act:** applications using `flattenUnionInput: true` with schema
|
|
807
|
-
walkers, snapshots or provider restrictions that assume there is no nested
|
|
808
|
-
`anyOf`. Applications without that assumption need only update the dependency.
|
|
809
|
-
|
|
810
|
-
Colliding object/array fields retain their complete structural alternatives:
|
|
811
|
-
|
|
812
|
-
```ts
|
|
813
|
-
// Before: loses both field vocabulary and array items.
|
|
814
|
-
{ type: ['object', 'array'] }
|
|
815
|
-
// After: structural alternatives live inside the property schema.
|
|
816
|
-
{ anyOf: [
|
|
817
|
-
{ type: 'array', items: {
|
|
818
|
-
type: 'object', properties: { id: { type: 'string' } }, required: ['id'],
|
|
819
|
-
} },
|
|
820
|
-
{ type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
|
|
821
|
-
] }
|
|
822
|
-
```
|
|
823
|
-
|
|
824
|
-
Alternatives come in kind order, independent of variant order. The same shape
|
|
825
|
-
covers every collision of different kinds, scalars included: `string` against
|
|
826
|
-
`string[]` becomes `{ anyOf: [{ type: 'array', items: { type: 'string' } }, { type:
|
|
827
|
-
'string' }] }`. A nullable field keeps `null` in each alternative's `type`, and
|
|
828
|
-
differing nested object unions remain nested `anyOf` inside the alternatives. Update
|
|
829
|
-
snapshots to this order.
|
|
830
|
-
|
|
831
|
-
Traverse each nested alternative when inspecting schemas and regenerate schema
|
|
832
|
-
snapshots. Verify the actual manifest with the chosen provider. Keep flatten
|
|
833
|
-
enabled when a top-level discriminated union needs to be advertised as an
|
|
834
|
-
object; do not erase `items` or introduce consumer-specific schema patches.
|
|
835
|
-
The root object, discriminator enum, requiredness hints and strict runtime
|
|
836
|
-
contract validation are unchanged. See ADR 0205.
|
|
837
|
-
|
|
838
|
-
## Released migration: 0.98.0
|
|
839
|
-
|
|
840
|
-
### automatic stdin availability
|
|
841
|
-
|
|
842
|
-
**Who must act:** CLI applications or scripts relying on automatic stdin routing
|
|
843
|
-
from a producer that can take more than 250 ms to write its first byte.
|
|
844
|
-
|
|
845
|
-
Automatic routing now probes for the first byte for 250 ms. An empty open pipe
|
|
846
|
-
leaves the required field unset and normal validation reports its name. Once
|
|
847
|
-
data arrives, the entire stream is read until EOF without a total deadline.
|
|
848
|
-
|
|
849
|
-
```sh
|
|
850
|
-
# before: may now fail if the producer starts late
|
|
851
|
-
slow-producer | myapp generate
|
|
852
|
-
# after: provide the value explicitly after the producer completes
|
|
853
|
-
myapp generate --prompt "$(slow-producer)"
|
|
854
|
-
```
|
|
855
|
-
|
|
856
|
-
For large input that should stay in a pipe, supply a `CliConfig.stdin` reader
|
|
857
|
-
that waits for the producer and EOF; this existing hook is not wrapped in the
|
|
858
|
-
availability probe. A custom reader owns its limits and error handling.
|
|
859
|
-
`-` remains literal data. TTY and empty EOF produce ordinary validation errors;
|
|
860
|
-
explicit arguments skip stdin. JSONL stream/batch commands, MCP/serve and
|
|
861
|
-
long-running handlers have no new deadline. See ADR 0203.
|
|
862
|
-
|
|
863
|
-
## Released migration: 0.97.0
|
|
864
|
-
|
|
865
|
-
### schedule delivery
|
|
866
|
-
|
|
867
|
-
**Who must act:** consumers of `createAgentScheduleService`, exhaustive switches on
|
|
868
|
-
`AgentSchedule.state`, and operators opening existing agent-runtime SQLite stores.
|
|
869
|
-
|
|
870
|
-
- Stop old scheduler processes and back up SQLite before startup. The store migrates
|
|
871
|
-
transactionally to v4; old binaries cannot open it. Rollback uses the backup.
|
|
872
|
-
- Handle `failed` as a terminal schedule state. To refuse future delivery (including
|
|
873
|
-
recurring delivery), return `{ status: 'terminal', reason: 'destination closed' }`.
|
|
874
|
-
The occurrence remains unadvanced; `schedule/failed` carries `terminal: true`.
|
|
875
|
-
- A throw or `{ status: 'retry', reason: 'temporarily unavailable' }` retries the same
|
|
876
|
-
key after 1, 2, 4, 8, 16, 32, then 60 seconds. `nextAt` remains the original due time;
|
|
877
|
-
`retryAt`, `attempts` and `lastError` expose retry diagnostics. Success clears them.
|
|
878
|
-
- Resolve `void` only after durable consumer admission. Honor the supplied `signal`:
|
|
879
|
-
dispatch has a 30-second deadline and a 60-second lease. Durable deduplication by
|
|
880
|
-
`idempotencyKey` is required even after abort or crash; do not implement sleeps in dispatch.
|
|
881
|
-
- Stale/cancelled attempts emit no `schedule/fired` or `schedule/late`. Observe the
|
|
882
|
-
cancellation event instead of relying on a synthetic cancelled firing.
|
|
883
|
-
|
|
884
|
-
```ts
|
|
885
|
-
// before
|
|
886
|
-
async function dispatch(request) {
|
|
887
|
-
if (await destinationClosed(request.conversationId)) throw new Error('closed')
|
|
888
|
-
await admit(request)
|
|
889
|
-
}
|
|
890
|
-
// after: use this callback as createAgentScheduleService({ dispatch, ... })
|
|
891
|
-
async function dispatch(request) {
|
|
892
|
-
if (await destinationClosed(request.conversationId)) {
|
|
893
|
-
return { status: 'terminal', reason: 'destination closed' }
|
|
894
|
-
}
|
|
895
|
-
await admit({ ...request, signal: request.signal }) // durably deduplicates idempotencyKey
|
|
896
|
-
}
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
## Released migration: 0.96.0
|
|
900
|
-
|
|
901
|
-
### resource-context-admission
|
|
902
|
-
|
|
903
|
-
One breaking change in `stitchkit/application`: `ManagedResourceContext` gains a
|
|
904
|
-
required `admission`. Every context the kernel hands a resource already carries
|
|
905
|
-
it; only a context built by hand needs it.
|
|
906
|
-
|
|
907
|
-
1. **Give a hand-built resource context an `admission`.** **Who must act:** a
|
|
908
|
-
project whose tests call a resource's phase directly with a context object
|
|
909
|
-
they wrote themselves (`resource.close?.({ applicationId, signal, … })`).
|
|
910
|
-
The type checker names each one:
|
|
911
|
-
|
|
912
|
-
```ts
|
|
913
|
-
// before
|
|
914
|
-
await resource.close?.({ applicationId: 'test', signal, now, reportHealth, use })
|
|
915
|
-
// after
|
|
916
|
-
await resource.close?.({
|
|
917
|
-
applicationId: 'test',
|
|
918
|
-
signal,
|
|
919
|
-
now,
|
|
920
|
-
reportHealth,
|
|
921
|
-
use,
|
|
922
|
-
admission: { acquire: () => null, acquireWhenAccepting: () => new Promise(() => {}) },
|
|
923
|
-
})
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
A test that needs real admission starts the resource inside
|
|
927
|
-
`createApplication` instead.
|
|
928
|
-
|
|
929
|
-
2. **Nothing to do for grammY polling, but know what changed.** **Who must
|
|
930
|
-
act:** nobody. `grammyPollingResource` now admits updates by batch: it does
|
|
931
|
-
not fetch while the application is not accepting, and finishes the batch in
|
|
932
|
-
hand before `bot.stop()`. A project that wrote its own `getUpdates`
|
|
933
|
-
transformer for this (waiting on `app.admission` before returning a batch)
|
|
934
|
-
deletes it; keeping both waits twice and is harmless.
|
|
@@ -1,14 +1,220 @@
|
|
|
1
|
-
# Upgrading 0.37.0 → 0.
|
|
1
|
+
# Upgrading 0.37.0 → 0.58.0 — stitchkit docs slice
|
|
2
2
|
|
|
3
3
|
> Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
|
|
4
4
|
> `llms.txt` in this package.
|
|
5
5
|
|
|
6
|
-
Contains: Guide: Upgrading, 0.37.0 → 0.
|
|
6
|
+
Contains: Guide: Upgrading, 0.37.0 → 0.58.0 (docs/guide/upgrading.md)
|
|
7
7
|
|
|
8
8
|
==============================================================================
|
|
9
|
-
# Guide: Upgrading, 0.37.0 → 0.
|
|
9
|
+
# Guide: Upgrading, 0.37.0 → 0.58.0 (docs/guide/upgrading.md)
|
|
10
10
|
==============================================================================
|
|
11
11
|
|
|
12
|
+
## Released migration: 0.58.0
|
|
13
|
+
|
|
14
|
+
### The default history projection rejects invalid chronology
|
|
15
|
+
|
|
16
|
+
`projectAgentHistory` no longer forwards records a provider contract cannot
|
|
17
|
+
accept. A completed assistant record before the first user message, and an
|
|
18
|
+
assistant record whose tool calls have no matching results, are omitted with an
|
|
19
|
+
inspectable decision instead of being sent upstream:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// before — a leading assistant record was forwarded as-is
|
|
23
|
+
projectAgentHistory(messages)
|
|
24
|
+
// after — opt in explicitly, and only where the provider contract permits it
|
|
25
|
+
projectAgentHistory(messages, { leadingAssistant: 'allow' })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Two consequences the changelog does not spell out. First, the projection can now
|
|
29
|
+
return **fewer** messages than the history holds, so any assertion or metric
|
|
30
|
+
that compared projected length against stored length will move; read the
|
|
31
|
+
detailed projection instead — it reports what was omitted and why, which is the
|
|
32
|
+
supported way to see the difference. Second, the omission is silent to the
|
|
33
|
+
provider but not to you: if a conversation suddenly loses its leading context,
|
|
34
|
+
the decision record is where that shows up, not the transcript.
|
|
35
|
+
|
|
36
|
+
### Operator events redact `internalCause` by default
|
|
37
|
+
|
|
38
|
+
Raw provider and tool failures no longer travel in operator events unless the
|
|
39
|
+
sink asks for them:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// before — internalCause was present
|
|
43
|
+
createAgentObservability({ write })
|
|
44
|
+
// after — an explicit operator-only opt-in
|
|
45
|
+
createAgentObservability({ write, includeInternalCause: true })
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The consequence to check before upgrading: any dashboard, alert or log
|
|
49
|
+
processor keyed on `internalCause` goes blind the moment you upgrade, and it
|
|
50
|
+
goes blind quietly — the field is absent, not empty. Set the flag on the
|
|
51
|
+
operator sink you own. Product delivery stays redacted regardless of the flag;
|
|
52
|
+
this option cannot widen what reaches a user.
|
|
53
|
+
|
|
54
|
+
## Released migration: 0.57.0
|
|
55
|
+
|
|
56
|
+
### Duplicate admission results carry the complete identity
|
|
57
|
+
|
|
58
|
+
Custom `AgentRuntimeStore` adapters must persist and return the input and
|
|
59
|
+
assistant identities associated with an idempotency key:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// before
|
|
63
|
+
return { outcome: 'duplicate', runId, snapshot }
|
|
64
|
+
|
|
65
|
+
// after
|
|
66
|
+
return { outcome: 'duplicate', input, inputMessageId, runId, assistantMessageId, snapshot }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Prefer replacing the custom aggregate reducer with `createAgentRuntimeStore()`;
|
|
70
|
+
its admission record and transaction driver implement this contract
|
|
71
|
+
automatically. Historical note: 0.59.0 reshaped this driver again, so an adapter
|
|
72
|
+
crossing both versions should read that section first and migrate once.
|
|
73
|
+
|
|
74
|
+
### `AgentRuntimeEvent` adds a post-commit `admission` variant
|
|
75
|
+
|
|
76
|
+
Add it to any exhaustive publisher switch. Its `assistant` is either the pending
|
|
77
|
+
placeholder for a new assignment or the canonical persisted assistant for a
|
|
78
|
+
duplicate:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
case 'admission':
|
|
82
|
+
await persistProductProjection(event.input, event.run, event.assistant)
|
|
83
|
+
break
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The consequence for an exhaustive switch written without a `default` branch is a
|
|
87
|
+
compile error, which is the point. The consequence for a switch that *has* a
|
|
88
|
+
`default` is worse and silent: post-commit admissions fall into it and are
|
|
89
|
+
projected as an unknown event. Grep for publisher switches before upgrading.
|
|
90
|
+
|
|
91
|
+
## Released migration: 0.56.0
|
|
92
|
+
|
|
93
|
+
### Surface manifests are version 2
|
|
94
|
+
|
|
95
|
+
`operation.tools` could not describe a role-selected MCP surface, a different
|
|
96
|
+
Agent or CLI selection, or an advertised `extend` schema, so projections moved
|
|
97
|
+
out of the canonical operation:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// before
|
|
101
|
+
manifest.operations[0].tools.MCP
|
|
102
|
+
// after
|
|
103
|
+
manifest.toolSurfaces.find((s) => s.transport === 'MCP' && s.surface === null)?.tools
|
|
104
|
+
|
|
105
|
+
// before
|
|
106
|
+
buildSurfaceManifest({ mcpSurfaces: { admin: { services, extend } } })
|
|
107
|
+
// after
|
|
108
|
+
buildSurfaceManifest({ mcpSurfaces: { admin: { services } }, mcpPreparation: { extend } })
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
A committed snapshot is regenerated once, deliberately: `manifestVersion` is
|
|
112
|
+
`2`, and `ConformanceTransport` gained `REALTIME`, so any exhaustive
|
|
113
|
+
`Record<ConformanceTransport, …>` must handle it.
|
|
114
|
+
|
|
115
|
+
### `FILE_*` codes joined the error registry
|
|
116
|
+
|
|
117
|
+
`StitchErrorCode` gained `FILE_INVALID_PATH`, `FILE_OUTSIDE_ROOT`,
|
|
118
|
+
`FILE_NOT_FOUND`, `FILE_NOT_REGULAR`, `FILE_INSPECTION_REJECTED`,
|
|
119
|
+
`FILE_TOO_LARGE` and `FILE_EXISTS`. Only exhaustive maps break:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
// before — compiled while the registry had no file codes
|
|
123
|
+
const copy: Record<StitchErrorCode, string> = { …, RATE_LIMITED: '…' }
|
|
124
|
+
// after — add the seven managed-file codes (or use Partial<Record<…>>)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Unexpected IO stays scrubbed as `INTERNAL_SERVER_ERROR`: the new codes are the
|
|
128
|
+
caller-safe ones only.
|
|
129
|
+
|
|
130
|
+
### `ScopedAuthHook` is nominal
|
|
131
|
+
|
|
132
|
+
A hand-written function shaped like an auth hook is no longer assignable —
|
|
133
|
+
identity now comes from the factory, so scope ownership and the inferred
|
|
134
|
+
context cannot drift apart:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// before — a structural stand-in
|
|
138
|
+
const auth: ScopedAuthHook<Scopes> = async (ctx, endpoint) => { … }
|
|
139
|
+
// after — create it, then compose domains
|
|
140
|
+
const auth = createAuthHook({ resolve, rules })
|
|
141
|
+
const composed = composeAuthHooks({ hooks: [auth, billingAuth], defaultScope: 'public' })
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Managed-file inspectors also run on reads
|
|
145
|
+
|
|
146
|
+
An inspector is no longer write-only, and it has a finite default deadline
|
|
147
|
+
(15 s). Make it read-aware and idempotent — a read carries no
|
|
148
|
+
`declaredMediaType`:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// before
|
|
152
|
+
inspect: ({ declaredMediaType }) => inspectDeclaredType(declaredMediaType!)
|
|
153
|
+
// after
|
|
154
|
+
inspect: ({ prefix, declaredMediaType, signal }) =>
|
|
155
|
+
inspectBytes(prefix, { declaredMediaType, signal })
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Set `inspectionTimeoutMs` explicitly when 15 seconds is the wrong budget.
|
|
159
|
+
|
|
160
|
+
### Direct async-operation binding needs a wire-stable ID
|
|
161
|
+
|
|
162
|
+
A direct binding reuses the start output as the follow-up wire input, so the ID
|
|
163
|
+
schema must parse to itself (`z.input` equals `z.output`, no transform,
|
|
164
|
+
coercion, default or overwrite). Anything else is parsed twice:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// before — a transform silently ran on start and again on every follow-up
|
|
168
|
+
defineAsyncOperationContract({ binding: 'direct', id: z.string().transform(Number) })
|
|
169
|
+
// after — keep the wire shape, adapt explicitly
|
|
170
|
+
defineAsyncOperationContract({
|
|
171
|
+
binding: 'adapted',
|
|
172
|
+
id,
|
|
173
|
+
adapters: { idFromStart, inputFor },
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Released migration: 0.55.0
|
|
178
|
+
|
|
179
|
+
### Peer-free `implementRemote`
|
|
180
|
+
|
|
181
|
+
`implementRemote` now has one canonical, optional-peer-free owner. This keeps
|
|
182
|
+
MCP SDK and AI SDK modules out of CLI bundles that only proxy HTTP calls:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
// before
|
|
186
|
+
import { implementRemote } from 'stitchkit/tools'
|
|
187
|
+
// after
|
|
188
|
+
import { implementRemote } from 'stitchkit/remote'
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Managed file boundary and strict auth returns
|
|
192
|
+
|
|
193
|
+
Create one boundary during application bootstrap and pass the capability, never
|
|
194
|
+
a per-call directory or host path:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { createManagedFileBoundary } from 'stitchkit/files'
|
|
198
|
+
|
|
199
|
+
const files = await createManagedFileBoundary({ root: '/srv/app-files' })
|
|
200
|
+
|
|
201
|
+
// before
|
|
202
|
+
defineDownloadTool({ defaultDir: '/srv/app-files', resolveUrl, ...common })
|
|
203
|
+
defineUploadTool({ upload: (path) => provider.uploadFile(path), ...common })
|
|
204
|
+
defineViewFileTool({ baseDir: '/srv/app-files', ...common })
|
|
205
|
+
|
|
206
|
+
// after
|
|
207
|
+
defineDownloadTool({ files, resolveUrl, ...common })
|
|
208
|
+
defineUploadTool({ files, upload: ({ bytes }) => provider.upload(bytes), ...common })
|
|
209
|
+
defineViewFileTool({ files, ...common })
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Downloaded `path` is now relative to the boundary and MIME metadata is named
|
|
213
|
+
`mediaType`. Update raw `mountDownload`/`mountUpload`/`mountViewFile` configs the
|
|
214
|
+
same way. Auth predicates must explicitly return `true`, `false`, or a plain
|
|
215
|
+
object of context fields; replace accidental `undefined` fallthroughs with the
|
|
216
|
+
intended boolean.
|
|
217
|
+
|
|
12
218
|
## Released migration: 0.53.0
|
|
13
219
|
|
|
14
220
|
### Realtime `emit` returns `boolean` instead of `void`
|