stitchkit 0.90.8 → 0.91.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 (73) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/agent-runtime-coding-tools.js +4 -4
  3. package/dist/agent-runtime-harness.js +6 -6
  4. package/dist/agent-runtime-sandbox.js +4 -4
  5. package/dist/agent-runtime.js +18 -17
  6. package/dist/application-grammy.js +1 -1
  7. package/dist/application.js +3 -3
  8. package/dist/cli.js +7 -7
  9. package/dist/contract/errors-factory.d.ts +12 -0
  10. package/dist/contract/errors-factory.d.ts.map +1 -1
  11. package/dist/contract/errors.d.ts +35 -1
  12. package/dist/contract/errors.d.ts.map +1 -1
  13. package/dist/contract/index.d.ts +1 -1
  14. package/dist/contract/index.d.ts.map +1 -1
  15. package/dist/contract/index.js +4 -2
  16. package/dist/{index-nrqg8tks.js → index-0a349c33.js} +3 -3
  17. package/dist/{index-adj0pc9r.js → index-137jjs4v.js} +2 -2
  18. package/dist/{index-42f33bzd.js → index-13cjtmrw.js} +1 -1
  19. package/dist/{index-qd8gs6nf.js → index-13e1k7v9.js} +2 -2
  20. package/dist/{index-dggfq62w.js → index-1qvdwnp2.js} +10 -3
  21. package/dist/{index-scs3f1eg.js → index-2vtcmrj9.js} +8 -2
  22. package/dist/{index-xbppf54a.js → index-6kyksz9y.js} +12 -7
  23. package/dist/{index-aj9geeez.js → index-709nh7z7.js} +37 -17
  24. package/dist/{index-zkcqhxx8.js → index-a11g2kry.js} +1 -1
  25. package/dist/{index-zjze12x8.js → index-b971qjes.js} +1 -1
  26. package/dist/{index-03bvcv7r.js → index-bh70rk41.js} +3 -3
  27. package/dist/{index-bva395we.js → index-bv7mytab.js} +2 -2
  28. package/dist/{index-4yktadec.js → index-bx6yq2m4.js} +23 -495
  29. package/dist/{index-p48nc8g9.js → index-f4p4b8f6.js} +10 -11
  30. package/dist/index-kw9t5ayn.js +507 -0
  31. package/dist/{index-1ft2wrv0.js → index-mqhjmadk.js} +43 -21
  32. package/dist/{index-3yqgj323.js → index-qjeyx5t2.js} +2 -2
  33. package/dist/{index-gte38nbm.js → index-qmy6zq75.js} +1 -1
  34. package/dist/{index-w58rfj2d.js → index-qrbdq8jk.js} +1 -1
  35. package/dist/{index-mthq65vn.js → index-ya611vx3.js} +4 -4
  36. package/dist/{index-xrwgzjw8.js → index-zsqy3xfn.js} +5 -5
  37. package/dist/index.js +8 -6
  38. package/dist/node.js +6 -6
  39. package/dist/observability/bounded-logger.d.ts +2 -0
  40. package/dist/observability/bounded-logger.d.ts.map +1 -1
  41. package/dist/observability/index.js +7 -4
  42. package/dist/observability/sanitize.d.ts +15 -0
  43. package/dist/observability/sanitize.d.ts.map +1 -1
  44. package/dist/primitives.js +2 -2
  45. package/dist/remote.js +5 -5
  46. package/dist/server/implement.d.ts +33 -7
  47. package/dist/server/implement.d.ts.map +1 -1
  48. package/dist/server/index.d.ts +1 -1
  49. package/dist/server/index.d.ts.map +1 -1
  50. package/dist/server/index.js +8 -8
  51. package/dist/testing.js +6 -6
  52. package/dist/tool-invoker.js +6 -6
  53. package/dist/tools/agent.d.ts +12 -0
  54. package/dist/tools/agent.d.ts.map +1 -1
  55. package/dist/tools/cli-format.d.ts +1 -1
  56. package/dist/tools/connections/index.js +1 -1
  57. package/dist/tools/durability-context.d.ts +1 -1
  58. package/dist/tools/durability-context.d.ts.map +1 -1
  59. package/dist/tools/durability-port.d.ts +55 -0
  60. package/dist/tools/durability-port.d.ts.map +1 -0
  61. package/dist/tools/execute.d.ts +59 -1
  62. package/dist/tools/execute.d.ts.map +1 -1
  63. package/dist/tools/mount.d.ts +2 -2
  64. package/dist/tools/mount.d.ts.map +1 -1
  65. package/dist/tools/runtime-tool.d.ts +1 -1
  66. package/dist/tools/runtime-tool.d.ts.map +1 -1
  67. package/dist/tools.d.ts +4 -0
  68. package/dist/tools.d.ts.map +1 -1
  69. package/dist/tools.js +21 -14
  70. package/dist/tracking.js +2 -2
  71. package/llms-full.txt +203 -8
  72. package/package.json +2 -2
  73. package/dist/index-7rkhw9ec.js +0 -34
package/llms-full.txt CHANGED
@@ -66,11 +66,11 @@ own, recorded as an ADR.
66
66
  | `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
67
67
  | `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
68
68
  | `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
69
- | `stitchkit/observability` | server | stable<br>_redefined in 1 of the 35 minors since 0.56.2, most recently 0.83.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
69
+ | `stitchkit/observability` | server | stable<br>_redefined in 1 of the 36 minors since 0.56.2, most recently 0.83.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
70
70
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
71
71
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
72
72
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
73
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 20 of the 35 minors since 0.56.2, most recently 0.89.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
73
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 20 of the 36 minors since 0.56.2, most recently 0.89.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
74
74
  | `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
75
75
  | `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults and deterministic race controls |
76
76
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
@@ -80,7 +80,7 @@ own, recorded as an ADR.
80
80
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
81
81
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
82
82
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
83
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the 35 minors since 0.56.2, most recently 0.83.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
83
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the 36 minors since 0.56.2, most recently 0.83.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
84
84
  | `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
85
85
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
86
86
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -1252,6 +1252,43 @@ scope** — the same prefix under two different group scopes is legal (their URL
1252
1252
  are separated by `scopePrefixes`, and a genuine path clash is the router's own
1253
1253
  construction error).
1254
1254
 
1255
+ #### A dev stand that survives half a feature — `onMissingHandler`
1256
+
1257
+ Adding an endpoint is two edits by construction: the contract, then the handler.
1258
+ An editor saves one file at a time and a watcher restarts on the first save, so
1259
+ between the two the application refuses to start — and on a shared dev stand that
1260
+ is everyone's stand, not just yours. A project rule ("make both edits at once")
1261
+ cannot outrun the filesystem.
1262
+
1263
+ ```ts
1264
+ implementRegistry(apiContractRegistry, handlers, {
1265
+ onMissingHandler: process.env.NODE_ENV === 'production' ? 'throw' : 'stub',
1266
+ })
1267
+ ```
1268
+
1269
+ With `'stub'` the application starts, the unimplemented endpoint answers `501` and
1270
+ names itself in the log at every mount, so it cannot be forgotten. Both save
1271
+ orders are covered: a contract ahead of its handler, and a handler ahead of its
1272
+ contract. The option is accepted by `implement`, `implementRegistry`,
1273
+ `createImplement`, `createScopedImplement`, `createImplementRegistry` and
1274
+ `createScopedImplementRegistry`.
1275
+
1276
+ Three things it deliberately does not do. It stays `'throw'` by default, and
1277
+ `'throw'` is the only right answer in production: a release is atomic, so there is
1278
+ no window to protect, and a surface with a hole must not ship. It does not relax
1279
+ the **compile-time** exactness check — that lives in the generic constraint, your
1280
+ editor still shows the missing handler, and a watcher that only transpiles is
1281
+ precisely why the runtime refusal was the one that bit. And it does not stub a
1282
+ streaming multipart endpoint, which needs `defineMultipartStream()` receivers; the
1283
+ stand still stops on one of those.
1284
+
1285
+ The stub answers `NOT_IMPLEMENTED` with status `501` everywhere — over HTTP and on
1286
+ a tool call that crosses a process boundary alike, because the code is registered
1287
+ in `STITCH_ERROR_STATUS`. One consequence follows from that and is worth knowing:
1288
+ a stubbed endpoint keeps its `expose`, so on a dev stand it is advertised to a
1289
+ model as a real tool that always refuses. The refusal is not retryable, so a model
1290
+ stops rather than loops; it still costs one turn to find out.
1291
+
1255
1292
  The registry is intentionally flat: every key must point to one concrete
1256
1293
  `defineContract()` result. Composed namespace arrays are mounted explicitly with
1257
1294
  `implement()` because they do not have a one-key-to-one-handler-map boundary.
@@ -3482,6 +3519,102 @@ schema and annotations, and nothing else — it moves when the contract moves an
3482
3519
  not when a handler does. Raw SDK registrations stay consumer-owned and are
3483
3520
  outside it.
3484
3521
 
3522
+ ### Shaping arguments before validation — `beforeToolCall`
3523
+
3524
+ `beforeToolCall` sees the arguments as they arrived and may **replace** them:
3525
+
3526
+ ```ts
3527
+ hooks: {
3528
+ beforeToolCall: async ({ args }) =>
3529
+ typeof args.id === 'string' && args.id.startsWith('@')
3530
+ ? { ...args, id: await resolveReference(args.id) }
3531
+ : undefined, // undefined / null / nothing → unchanged
3532
+ }
3533
+ ```
3534
+
3535
+ The replacement is validated by the contract schema like any other input, so this
3536
+ shapes a call and never bypasses the schema — a hook returning something the
3537
+ contract refuses produces an ordinary `VALIDATION_ERROR`. It runs before the
3538
+ params/input split, before argument coercion and before both schemas, which is
3539
+ why a field declared `z.string()` can receive a reference and still be a string by
3540
+ the time it is checked. `lifecycle.beforeHandle` is too late for this: the schemas
3541
+ have already answered.
3542
+
3543
+ The audit trail is unaffected. `afterToolCall` reports what the caller sent as
3544
+ `args` and the replacement separately as `effectiveArgs`, so a rewrite is visible
3545
+ as a rewrite rather than by overwriting the evidence.
3546
+
3547
+ ### Knowing the outcome of a call — `afterToolCall` and `toolCallId`
3548
+
3549
+ `afterToolCall` receives the whole `ToolResult` — `{ok: true, data}` or
3550
+ `{ok: false, code, …}` — on every exit, including a validation failure that never
3551
+ reached the handler, and on all three transports. It is the answer to "did this
3552
+ call fail"; deriving that from the serialized error text instead is the mistake
3553
+ this section exists to prevent, and it is a mistake that does not announce itself:
3554
+ one consumer had five places asking the question and getting five answers.
3555
+
3556
+ To correlate an outcome with the call the model made, read `context.toolCallId`:
3557
+
3558
+ ```ts
3559
+ hooks: {
3560
+ afterToolCall: ({ context, result }) => {
3561
+ loop.record(context.toolCallId, result.ok ? 'ok' : result.code);
3562
+ },
3563
+ }
3564
+ ```
3565
+
3566
+ Two exits are deliberately **not** reported: a runtime tool presenter that throws
3567
+ on the agent surface (the AI SDK calls it after `execute` has already returned)
3568
+ and `ToolExecutionControlError`, which is a run being stopped rather than a tool
3569
+ failing — check it with `isToolExecutionControlError`.
3570
+
3571
+ ### Restartable tool bodies — `durability`
3572
+
3573
+ A tool that starts slow work and must report exactly once, including after a host
3574
+ restart, can keep that bookkeeping **inside** the tool:
3575
+
3576
+ ```ts
3577
+ mountAgent(services, {
3578
+ durability: (toolCallId, signal) => myLedgerFor(toolCallId, signal),
3579
+ });
3580
+
3581
+ // in the handler
3582
+ const asset = await ctx.step('render', () => renderAsset(ctx.input));
3583
+ await ctx.waitFor({ event: 'render-done', id: assetId });
3584
+ ```
3585
+
3586
+ `ToolDurability` is the port a tool body sees — `step`, `sleep`, `waitFor`. You
3587
+ do not have to implement it: the engine ships from `stitchkit/tools`, and it needs
3588
+ a ledger of two methods over storage you already have:
3589
+
3590
+ ```ts
3591
+ import { createLocalStepDurability, type StepDurabilityLedger } from 'stitchkit/tools';
3592
+
3593
+ const ledger: StepDurabilityLedger = {
3594
+ appendEvent: (event) => db.durabilityEvents.insert(event), // returns the row with `seq`
3595
+ readEvents: ({ conversationId, fromSeq, toSeq }) => db.durabilityEvents.page(…),
3596
+ };
3597
+
3598
+ mountAgent(services, {
3599
+ durability: (toolCallId, signal) =>
3600
+ createLocalStepDurability({ store: ledger, conversationId, runId: toolCallId, signal }),
3601
+ });
3602
+ ```
3603
+
3604
+ That gives replay, absolute deadlines and decode refusal without writing them.
3605
+ The port is there for an application that already has its own ledger engine.
3606
+
3607
+ One thing the port deliberately leaves out: **delivery**. `waitFor` parks until
3608
+ `{ event, id }` arrives, and a tool body has no business delivering to itself —
3609
+ so `deliver` lives on the engine object you constructed, not in the handler
3610
+ context. Whatever produces the event (a webhook, a queue consumer, a cron) calls
3611
+ `engine.deliver({ event, id, payload })` against the same ledger; the parked
3612
+ `waitFor` resolves, and a `waitFor` that starts after the delivery returns at
3613
+ once. Taking `agent-runtime` is not required and nothing of the runtime proper
3614
+ enters the `stitchkit/tools` graph. An application that *does* run on
3615
+ `agent-runtime` gets durability through the run context and needs no option;
3616
+ where both exist the runtime's wins, because the run is recorded in its ledger.
3617
+
3485
3618
  ### Typed MCP call metadata
3486
3619
 
3487
3620
  Contract handlers, runtime-tool handlers/factories, lifecycle and tool hooks all
@@ -5511,7 +5644,7 @@ or it keeps the failed attempt's partial text on screen.
5511
5644
  usage, files and sources do not count;
5512
5645
  - `tool-status` is transient lifecycle presentation with JSON-safe input on
5513
5646
  start and output on completion. A mounted typed failure carries the same safe
5514
- `{ error, details?, _hint? }` envelope as the durable result; an unknown
5647
+ `{ error, retryable, details?, _hint? }` envelope as the durable result; an unknown
5515
5648
  internal cause remains generic and stays in local observability only;
5516
5649
  - `terminal` follows the winning terminal CAS.
5517
5650
 
@@ -9903,7 +10036,8 @@ core never models them (ADR 0002). But stitchkit itself emits a set of its own:
9903
10036
  `CONFLICT`, `RATE_LIMITED`, `VALIDATION_ERROR`, `FILE_INVALID_PATH`,
9904
10037
  `FILE_OUTSIDE_ROOT`, `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`,
9905
10038
  `FILE_INSPECTION_REJECTED`, `FILE_TOO_LARGE`, `FILE_EXISTS`,
9906
- `REALTIME_CONTRACT_VIOLATION`, `APPLICATION_NOT_ACCEPTING`, `WAIT_TIMEOUT`,
10039
+ `REALTIME_CONTRACT_VIOLATION`, `APPLICATION_NOT_ACCEPTING`, `NOT_IMPLEMENTED`,
10040
+ `WAIT_TIMEOUT`,
9907
10041
  `WAIT_FAILED`, `DOWNLOAD_NOT_FOUND`, `VIEW_HTTP_ERROR`,
9908
10042
  `OPERATION_NOT_SUCCEEDED`, `STREAM_ITEM_INVALID`, `STREAM_FRAME_TOO_LARGE`,
9909
10043
  `STREAM_TERMINAL_MISSING`, `STREAM_LIFETIME_EXCEEDED`,
@@ -10031,7 +10165,7 @@ whether the code declares `details`:
10031
10165
  | code **with** a `details` schema | the declared message | no text at all — only `details` and `hint` |
10032
10166
  | code **without** one | the declared message | the same text, delivered as `details.message` |
10033
10167
 
10034
- The model-facing envelope is `{ error, details?, _hint? }`; for a code with no
10168
+ The model-facing envelope is `{ error, retryable, details?, _hint? }`; for a code with no
10035
10169
  details schema the framework fills `details` with `{ message }`, so declaring a
10036
10170
  message changes what the model reads there — it used to be the code itself. Put
10037
10171
  anything the model must reliably read in `details` or `hint`, not in `message`.
@@ -11003,6 +11137,24 @@ applied. Active trace, span, user and dimensions are reserved framework fields;
11003
11137
  caller data cannot overwrite them. A throwing getter, cyclic value or failing
11004
11138
  sink never escapes into application code.
11005
11139
 
11140
+ One string bound for the whole record cuts an error's stack from the bottom —
11141
+ which is exactly where your own frames are. Raise the ceiling for that key alone,
11142
+ wherever it sits (`stack` at the top of one record and under `errorDetail` in the
11143
+ next is the same field), without touching the bound for everything else:
11144
+
11145
+ ```ts
11146
+ createBoundedLogger({
11147
+ sink,
11148
+ bounds: { stringLength: 4_000, stringLengthByKey: { stack: 32_000 } },
11149
+ })
11150
+ // or directly: redact(value, { maxStringLength: 4_000, maxStringLengthByKey: { stack: 32_000 } })
11151
+ ```
11152
+
11153
+ Keyed by name, not by path — the question is "what is this field", not "where is
11154
+ it". The entry ceiling (`entryBytes`) keeps the last word: a record that does not
11155
+ fit still collapses, but the decision is made for the record, not by cutting one
11156
+ field short in advance.
11157
+
11006
11158
 
11007
11159
  ==============================================================================
11008
11160
  # Guide: Testing & deployment (docs/guide/testing-and-deployment.md)
@@ -11971,6 +12123,23 @@ analytics policy belong to the application.
11971
12123
 
11972
12124
  # Upgrading stitchkit
11973
12125
 
12126
+ ## Released migration: 0.91.0
12127
+
12128
+ 1. `STITCH_ERROR_STATUS` gained `NOT_IMPLEMENTED` (501). An **exhaustive** error
12129
+ vocabulary must name it — without the row `defineErrors({ mapping: { exhaustive:
12130
+ true } })` stops compiling and then throws at startup. Nothing else in this
12131
+ release requires action.
12132
+
12133
+ ```ts
12134
+ // before
12135
+ map: { …, WAIT_TIMEOUT: 'timeout' }
12136
+ // after
12137
+ map: { …, NOT_IMPLEMENTED: 'not_implemented', WAIT_TIMEOUT: 'timeout' }
12138
+ ```
12139
+
12140
+ The code is thrown only by the new dev-only `onMissingHandler: 'stub'` policy;
12141
+ a vocabulary that is not exhaustive is unaffected.
12142
+
11974
12143
  ## Released migration: 0.90.0
11975
12144
 
11976
12145
  1. `stitchkit.watch.value` is a discriminated union on `kind` — `full`, `delta`,
@@ -15702,6 +15871,7 @@ from the root `stitchkit`.
15702
15871
  | `STITCH_ERROR_STATUS` | const | `code → HTTP status` map for stitchkit's own error codes — [guide](../guide/auth-and-errors.md#stitch-codes-vs-your-codes) |
15703
15872
  | `StitchErrorCode` | _type_ | a code stitchkit itself emits (`keyof STITCH_ERROR_STATUS`) |
15704
15873
  | `isStitchErrorCode` | function | type guard — is a code one of stitchkit's own? |
15874
+ | `isRetryableStatus` | function | whether a status class means "the same call could work later" — `408`, `429`, `502`, `503`, `504`, and deliberately not every `5xx` |
15705
15875
 
15706
15876
  ### Pagination
15707
15877
 
@@ -15813,6 +15983,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
15813
15983
  | `implementRegistry` | function | bind one exact contract registry to one exact backend handler registry |
15814
15984
  | `createImplementRegistry` | function | context-typed factory for `implementRegistry` |
15815
15985
  | `ImplementationRegistry` | _type_ | flat literal registry of concrete contracts accepted by `implementRegistry` |
15986
+ | `ImplementOptions` | _type_ | `{ onMissingHandler }` — how a binding treats a contract endpoint with no handler |
15987
+ | `MissingHandlerPolicy` | _type_ | `'throw'` (default) or `'stub'`, the dev-stand policy that mounts a `501` instead of refusing to start |
15816
15988
  | `KeyedServices` | _type_ | registry result: the mount-ordered `ServiceDef[]` carrying the same services under `.byKey` |
15817
15989
  | `RegistryHandlers` | _type_ | exact backend handler registry inferred from a contract registry |
15818
15990
  | `ExactRegistryHandlers` | _type_ | fail-first handler shape that rejects extra registry and endpoint keys |
@@ -16766,7 +16938,7 @@ audit event. See the [Observability guide](../guide/observability.md).
16766
16938
  | `DimensionCollision` / `SetRequestDimensionsOptions` | _type_ | explicit overwrite, preserve or error policy for dimension keys |
16767
16939
  | `createBoundedLogger` | function | decorate a `StitchLogger` with request context, shared sanitisation, redaction and total bounds |
16768
16940
  | `DEFAULT_REDACT_PATHS` | constant | baseline credential/token paths added to the sanitizer's sensitive-key policy |
16769
- | `BoundedLoggerBounds` / `BoundedLoggerOptions` | _type_ | per-value and total record ceilings plus sink/redaction configuration |
16941
+ | `BoundedLoggerBounds` / `BoundedLoggerOptions` | _type_ | per-value and total record ceilings (`stringLengthByKey` raises the string bound for named keys such as `stack`) plus sink/redaction configuration |
16770
16942
  | `RequestEvent` | _type_ | the normalised audit event handed to the sink; opt-in HTTP cancellation rows carry `outcome: 'cancelled'` |
16771
16943
  | `ObservabilityConfig` | _type_ | independent request and tool sink configuration |
16772
16944
  | `Observability` | _type_ | `{ request?, toolCall, getStatus(), flush(bound?): Promise<boolean>, close(bound?) }` with bounded sink lifecycle |
@@ -16820,7 +16992,7 @@ audit event. See the [Observability guide](../guide/observability.md).
16820
16992
  | `truncatePreview` | function | cap a value by serialised size |
16821
16993
  | `measureSize` | function | item count + byte size of a result |
16822
16994
  | `JsonValue` | _type_ | a JSON-serialisable value |
16823
- | `SanitizeOptions` | _type_ | tuning for `redact` / `sanitizePayload` |
16995
+ | `SanitizeOptions` | _type_ | tuning for `redact` / `sanitizePayload` — `maxStringLengthByKey` bounds a named key wherever it sits without changing the record-wide bound |
16824
16996
  | `SizeMeasure` | _type_ | the result of `measureSize` |
16825
16997
 
16826
16998
  ---
@@ -17044,6 +17216,29 @@ A native remote-connector auth surface for MCP — [guide](../guide/mcp-and-agen
17044
17216
  | `RefreshData` | _type_ | a stored refresh-token record |
17045
17217
  | `RegisteredClient` | _type_ | a registered OAuth client |
17046
17218
 
17219
+ ### Durable tool bodies
17220
+
17221
+ A mounted tool body can be made restartable without adopting `agent-runtime`: declare a
17222
+ factory and `step` / `sleep` / `waitFor` appear in the handler context, backed by whatever
17223
+ ledger the application already has.
17224
+
17225
+ ```ts
17226
+ mountAgent(services, { durability: (toolCallId) => myLedgerFor(toolCallId) });
17227
+ ```
17228
+
17229
+ | Export | Kind | Summary |
17230
+ |--------|------|---------|
17231
+ | `ToolDurability` | _type_ | the port a tool body needs: `step`, `sleep`, `waitFor` |
17232
+ | `ToolDurabilityFactory` | _type_ | `(toolCallId, signal) => ToolDurability`, called once per call |
17233
+ | `DurableJsonValue` | _type_ | what a durable step result may hold — a record is read back in another process, so it must be JSON |
17234
+ | `createLocalStepDurability` | function | the durability engine — replay, absolute deadlines, park/deliver — over a two-method ledger the application owns |
17235
+ | `StepDurabilityLedger` | _type_ | the two methods an engine needs: `appendEvent`, `readEvents` |
17236
+ | `LocalStepDurability` | _type_ | what the engine returns; satisfies `ToolDurability` and adds `deliver` and record introspection for the ledger's owner |
17237
+ | `LocalStepDurabilityOptions` | _type_ | `{ store, conversationId, runId, signal?, subscribe?, clock? }` |
17238
+ | `AppendAgentStoreEvent` / `ReadAgentStoreEvents` / `AgentStoreEventEnvelope` / `AgentStoreEventPage` | _type_ | the ledger's row shapes, re-exported so a ledger is typed from the entrypoint that consumes it |
17239
+ | `AgentToolError` | class | the model-safe envelope a failed agent tool throws |
17240
+ | `isAgentToolError` | function | brand check for it — a consumer running its own loop no longer parses the message |
17241
+
17047
17242
  ### Catalog stamp
17048
17243
 
17049
17244
  Every advertised tool and every tool result carries the fingerprint of the catalog it came
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.90.8",
3
+ "version": "0.91.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",
@@ -211,7 +211,7 @@
211
211
  "build:js": "bun run build:entrypoints && bun scripts/preserve-webpack-ignore.mjs",
212
212
  "build:types": "bun x tsc -p tsconfig.build.json --emitDeclarationOnly && bun scripts/rewrite-declaration-specifiers.mjs",
213
213
  "build": "bun ../../scripts/package-build-lock.ts -- bun run build:unlocked",
214
- "build:unlocked": "rm -rf dist && cp ../../CHANGELOG.md ./CHANGELOG.md && bun run build:js && chmod +x dist/upgrade-cli.js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-declarations-strict.mjs",
214
+ "build:unlocked": "rm -rf dist && cp ../../CHANGELOG.md ./CHANGELOG.md && bun run build:js && chmod +x dist/upgrade-cli.js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-durability-port-standalone.mjs && bun scripts/check-declarations-strict.mjs",
215
215
  "dev": "bun run build:entrypoints -- --watch",
216
216
  "prepack": "cp ../../README.md ./README.md && bun ../../scripts/gen-llms.ts && bun run build",
217
217
  "test": "bun test",
@@ -1,34 +0,0 @@
1
- // src/tools/agent-tool-error.ts
2
- var AGENT_TOOL_ERROR_BRAND = Symbol.for("stitchkit.AgentToolError");
3
-
4
- class AgentToolError extends Error {
5
- output;
6
- constructor(output, cause) {
7
- let message = "Tool execution failed";
8
- try {
9
- message = JSON.stringify(output);
10
- } catch {}
11
- super(message, cause === undefined ? undefined : { cause });
12
- this.name = "AgentToolError";
13
- this.output = output;
14
- Object.defineProperty(this, AGENT_TOOL_ERROR_BRAND, { value: true });
15
- }
16
- }
17
- function isAgentToolError(value) {
18
- return typeof value === "object" && value !== null && AGENT_TOOL_ERROR_BRAND in value;
19
- }
20
-
21
- // src/tools/durability-context.ts
22
- var contexts = new WeakMap;
23
- function createToolDurabilityContext(factory) {
24
- const context = {};
25
- contexts.set(context, factory);
26
- return context;
27
- }
28
- function resolveToolDurability(context, callId, signal) {
29
- if (typeof context !== "object" || context === null)
30
- return;
31
- return contexts.get(context)?.(callId, signal);
32
- }
33
-
34
- export { AgentToolError, isAgentToolError, createToolDurabilityContext, resolveToolDurability };