stitchkit 0.70.5 → 0.71.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 (76) hide show
  1. package/dist/agent-runtime/coding-tool-contract.d.ts +11 -1
  2. package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
  3. package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
  4. package/dist/agent-runtime/coding-tool-listing.d.ts +15 -0
  5. package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -0
  6. package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -1
  7. package/dist/agent-runtime/coding-tool-refusals.d.ts +59 -0
  8. package/dist/agent-runtime/coding-tool-refusals.d.ts.map +1 -0
  9. package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
  10. package/dist/agent-runtime/coding-tools.d.ts +3 -1
  11. package/dist/agent-runtime/coding-tools.d.ts.map +1 -1
  12. package/dist/agent-runtime/compaction.d.ts +35 -0
  13. package/dist/agent-runtime/compaction.d.ts.map +1 -1
  14. package/dist/agent-runtime/contained-files.d.ts +21 -2
  15. package/dist/agent-runtime/contained-files.d.ts.map +1 -1
  16. package/dist/agent-runtime/provider-failure.d.ts +66 -0
  17. package/dist/agent-runtime/provider-failure.d.ts.map +1 -0
  18. package/dist/agent-runtime/purge.d.ts +28 -0
  19. package/dist/agent-runtime/purge.d.ts.map +1 -0
  20. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  21. package/dist/agent-runtime/runtime.d.ts +31 -1
  22. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  23. package/dist/agent-runtime/schemas.d.ts +1 -0
  24. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  25. package/dist/agent-runtime/sqlite-purge.d.ts +7 -0
  26. package/dist/agent-runtime/sqlite-purge.d.ts.map +1 -0
  27. package/dist/agent-runtime/sqlite.d.ts.map +1 -1
  28. package/dist/agent-runtime/store-driver.d.ts +2 -0
  29. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  30. package/dist/agent-runtime/store-purge.d.ts +13 -0
  31. package/dist/agent-runtime/store-purge.d.ts.map +1 -0
  32. package/dist/agent-runtime/store.d.ts +3 -0
  33. package/dist/agent-runtime/store.d.ts.map +1 -1
  34. package/dist/agent-runtime-coding-tools.js +438 -124
  35. package/dist/agent-runtime-harness.js +4 -4
  36. package/dist/agent-runtime-openrouter.d.ts +19 -0
  37. package/dist/agent-runtime-openrouter.d.ts.map +1 -1
  38. package/dist/agent-runtime-openrouter.js +2 -1
  39. package/dist/agent-runtime-sqlite-bun.js +2 -2
  40. package/dist/agent-runtime-sqlite-node.js +2 -2
  41. package/dist/agent-runtime.d.ts +5 -2
  42. package/dist/agent-runtime.d.ts.map +1 -1
  43. package/dist/agent-runtime.js +128 -16
  44. package/dist/cli.js +5 -5
  45. package/dist/{index-cx84zg25.js → index-118v0z41.js} +1 -1
  46. package/dist/{index-jw81xr75.js → index-19eet1qx.js} +127 -51
  47. package/dist/{index-ejv31h14.js → index-7qy2ex0m.js} +1 -1
  48. package/dist/{index-9sx8tbz2.js → index-cksjz4eg.js} +50 -1
  49. package/dist/{index-trrf7nch.js → index-ezmn6ac6.js} +3 -3
  50. package/dist/{index-ekkt4gy9.js → index-jqtsc9mj.js} +2 -2
  51. package/dist/{index-hxtm2xr2.js → index-k2zczx1g.js} +1 -1
  52. package/dist/{index-35wcrke5.js → index-m668wzyc.js} +13 -2
  53. package/dist/{index-w9m1wznn.js → index-nemjkxjp.js} +118 -1
  54. package/dist/{index-tnazr5tc.js → index-pz7ytdga.js} +3 -3
  55. package/dist/{index-1z5xxfst.js → index-rqpdar1e.js} +102 -5
  56. package/dist/{index-qm87g51s.js → index-s4c8wy8m.js} +1 -1
  57. package/dist/node.js +2 -2
  58. package/dist/observability/index.js +1 -1
  59. package/dist/server/index.d.ts +1 -1
  60. package/dist/server/index.d.ts.map +1 -1
  61. package/dist/server/index.js +4 -2
  62. package/dist/server/request.d.ts +19 -0
  63. package/dist/server/request.d.ts.map +1 -1
  64. package/dist/telegram/init-data.d.ts +134 -0
  65. package/dist/telegram/init-data.d.ts.map +1 -0
  66. package/dist/telegram/send-failure.d.ts +73 -0
  67. package/dist/telegram/send-failure.d.ts.map +1 -0
  68. package/dist/telegram.d.ts +3 -0
  69. package/dist/telegram.d.ts.map +1 -0
  70. package/dist/telegram.js +224 -0
  71. package/dist/tool-invoker.js +4 -4
  72. package/dist/tools.js +7 -7
  73. package/llms-full.txt +332 -9
  74. package/native/darwin-arm64.node +0 -0
  75. package/native/darwin-x64.node +0 -0
  76. package/package.json +6 -2
package/llms-full.txt CHANGED
@@ -55,11 +55,12 @@ own, recorded as an ADR.
55
55
  | `stitchkit/cli` | server | stable | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
56
56
  | `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
57
57
  | `stitchkit/files` | server (Bun or Node) | stable | peer-free managed local-file boundary |
58
+ | `stitchkit/telegram` | server (Bun or Node) | evolving | peer-free Telegram platform primitives — Mini App `initData` verification and Bot API send-failure classification |
58
59
  | `stitchkit/observability` | server | stable | request/tool event projections — `createObservability`, trace context, sanitisation |
59
60
  | `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 |
60
61
  | `stitchkit/declaration` | build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
61
62
  | `stitchkit/react` | browser | stable | `createCursorQuery`, `createCacheBridge` |
62
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 10 of the 15 minors since 0.56.2, most recently 0.69.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
63
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 10 of the 16 minors since 0.56.2, most recently 0.69.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
63
64
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
64
65
  | `stitchkit/agent-runtime/coding-tools` | server (Bun or Node) | evolving | bounded host-authorized direct file and shell tools; a root boundary, not an OS sandbox |
65
66
  | `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
@@ -67,7 +68,7 @@ own, recorded as an ADR.
67
68
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
68
69
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
69
70
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
70
- | `stitchkit/application` | server | evolving<br>_redefined in 3 of the 15 minors since 0.56.2, most recently 0.67.0_ | managed resource graph, readiness, admission, schedules and bounded shutdown |
71
+ | `stitchkit/application` | server | evolving<br>_redefined in 3 of the 16 minors since 0.56.2, most recently 0.67.0_ | managed resource graph, readiness, admission, schedules and bounded shutdown |
71
72
  | `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
72
73
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
73
74
 
@@ -180,6 +181,7 @@ map — feature → packages:
180
181
  | **Socket.IO server on Node** | `socket.io` |
181
182
  | Socket.IO client | `socket.io-client` (runtime peer; unrelated root declarations remain peer-free) |
182
183
  | grammY lifecycle adapters (`stitchkit/application/grammy`) | `grammy` |
184
+ | Telegram platform primitives (`stitchkit/telegram`) | — (peer-free) |
183
185
  | OpenTelemetry gauges (`stitchkit/application/opentelemetry`) | `@opentelemetry/api` |
184
186
 
185
187
  ```bash
@@ -4101,7 +4103,8 @@ responses and results with a different call or tool name are invalid; dropping a
4101
4103
  approval input fails the run with a private diagnostic rather than starting a fresh model turn.
4102
4104
 
4103
4105
  `stitchkit/agent-runtime/coding-tools` returns ordinary direct runtime tools named `read_file`,
4104
- `write_file`, `search_files`, `apply_patch`, `run_command` and optional `read_output`. Every call passes a
4106
+ `write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, `run_command` and optional
4107
+ `read_output`. Every call passes a
4105
4108
  required host authorization callback. File paths are relative, bounded and contained after
4106
4109
  descriptor-relative resolution: each ancestor is opened without following symlinks and remains
4107
4110
  pinned through authorization and the filesystem effect. Reads revalidate the pinned file identity;
@@ -4117,10 +4120,32 @@ Arguments, output and time are bounded, while cancellation terminates the child.
4117
4120
  root and cwd are path boundaries, not a security sandbox: isolate the process when an executable
4118
4121
  must not access the rest of the machine.
4119
4122
 
4120
- `apply_patch` binds the exact source to `baseSha256`, supports dry-run, authorizes the exact
4121
- replacement count, result digest and byte size, then rechecks the base under a per-target lock
4122
- before same-directory atomic replacement. It deliberately does not claim
4123
- multi-file atomicity. With an optional `AgentCodingArtifactStore`, shell output beyond the inline
4123
+ `edit_file` replaces one exact snippet. `oldText` is itself the freshness guard for the region it
4124
+ changes, so the digest is the optional `expectedSha256` and an edit is one call; pass the digest
4125
+ `read_file` returned when you want the whole-file guarantee, and read the `sha256` it returns to
4126
+ chain the next edit without re-reading. The read, the occurrence count and the construction of the
4127
+ new content all happen inside one per-target lock, so two concurrent edits of different snippets in
4128
+ one file cannot each build a file from the same base and have the second erase the first — the lock
4129
+ is process-local, which covers many agents in one process and not two processes over one workspace.
4130
+ It authorizes the exact replacement count, result digest and byte size, and deliberately does not
4131
+ claim multi-file atomicity.
4132
+
4133
+ `write_file` creates missing parent directories inside the root and names them in
4134
+ `createdDirectories`; the walk that finds them runs before authorization and reports them in the
4135
+ authorization payload, so no directory is created before a host approves it.
4136
+
4137
+ **Every ordinary outcome is a refusal a model can act on** — a missing file is `NOT_FOUND`, an
4138
+ existing file without `overwrite` is `CONFLICT`, an ambiguous snippet is `CONFLICT` carrying its
4139
+ occurrence count, a path outside the root is `FORBIDDEN` — each with a `hint` naming the next move.
4140
+ Host-level causes stay scrubbed to `INTERNAL_SERVER_ERROR` and name nothing outside the workspace.
4141
+ → ADR 0139
4142
+
4143
+ `list_directory` marks excluded directories rather than hiding them, and `glob` reports
4144
+ `skippedDirectories` beside its matches: an empty result from a tree whose files all live under an
4145
+ excluded directory is not "no files", and a model told only "nothing found" concludes the wrong
4146
+ thing. `search_files` takes `regex`, `context` lines and an `include` pattern; regex is bounded by
4147
+ refusing backreferences and lookaround and capping line length rather than by a timeout, because a
4148
+ JavaScript `RegExp` cannot be interrupted once it starts backtracking. With an optional `AgentCodingArtifactStore`, shell output beyond the inline
4124
4149
  preview continues into an opaque bounded artifact and `read_output` reads slices without
4125
4150
  exposing a host path. Without a store, the previous finite output-limit behavior is unchanged.
4126
4151
 
@@ -4548,6 +4573,61 @@ boundary, validate with the exported schema, and write only the current
4548
4573
  version. Core deliberately does not guess an application's database migration
4549
4574
  or silently accept an unknown future version.
4550
4575
 
4576
+ ### Purging a conversation
4577
+
4578
+ ```ts
4579
+ import { purgeAgentConversation } from 'stitchkit/agent-runtime';
4580
+
4581
+ const result = await purgeAgentConversation(store, {
4582
+ conversationId,
4583
+ expectedVersion: snapshot.version, // optional stale-intent protection
4584
+ });
4585
+ ```
4586
+
4587
+ The optional `AgentRuntimeStore.purgeConversation` capability is implemented by the memory and
4588
+ initialized Bun/Node SQLite stores. The helper returns `unsupported` for stores without it; it
4589
+ never pretends deletion succeeded. Other outcomes are `purged`, `already_purged`,
4590
+ `active` (with `runIds`), or `conflict` (with `actualVersion`). Storage failures reject.
4591
+
4592
+ Purge refuses queued, running and interrupt-requested runs. It does not interrupt, force-abandon
4593
+ or wait for a provider. Authorize the destructive request in the host, close its conversation
4594
+ ingress/attachments, then settle runs through normal interruption or explicitly justified recovery.
4595
+ Retry purge after they are terminal. An optional expected version must match the current snapshot;
4596
+ a version conflict requires a fresh deletion decision, not blind retry with a new version.
4597
+
4598
+ Successful purge atomically removes messages (including compacted inactive rows), all runs and
4599
+ retained terminal assistants, admissions/idempotency receipts, conversation heads and their owned
4600
+ indexes. It retains only a permanent conversation-ID tombstone. Purging an unknown ID reserves it
4601
+ as well, so a submit paused before admission cannot create it later. Repeating the call returns
4602
+ `already_purged`, regardless of the original expected version. A lost response is safely retried
4603
+ with the same ID; **new chats require a new ID**.
4604
+
4605
+ Every later runtime mutation rejects with `AgentConversationPurgedError`, including fresh and
4606
+ duplicate admissions, checkpoints, recovery and compaction. Existing controller leases grant no
4607
+ exception. Existing reads return empty snapshots, absent runs and empty message pages; purged
4608
+ conversations disappear from the SQLite catalog and recovery scans. Snapshots retain their
4609
+ existing empty `version: 0` shape; use the purge result, not an empty snapshot, as deletion evidence.
4610
+
4611
+ SQLite commits the tombstone and all deletions in one `BEGIN IMMEDIATE` transaction. Failure before
4612
+ commit rolls everything back; reopen/recovery cannot restore committed deleted records. Same-thread
4613
+ connection contention rejects promptly without effects; retry after the competing operation settles.
4614
+ Initialization adds the tombstone table and write guards to schema v1 transactionally, without
4615
+ changing application tables or `user_version`. Guards also fence older writers. `initialize: false`
4616
+ on an original v1 database does not add purge capability; open an initialized writer first.
4617
+
4618
+ Consumer-owned model selections, projections, attachment files, delivery/outbox records and event
4619
+ logs remain outside this boundary. Retain an authorized cleanup intent plus any needed opaque file
4620
+ references in consumer storage, purge first, then retry idempotent consumer cleanup. Invalidate local
4621
+ caches/subscriptions so an old delivered event cannot repopulate a UI. The library never traverses
4622
+ paths, revokes remote artifacts or claims a cross-store transaction. Nor does logical purge securely
4623
+ wipe database free pages, WAL or backups. Memory tombstones last only for that store instance.
4624
+
4625
+ Custom normalized drivers may opt in through `AgentConversationPurgeDriver` on `driver.conversations`.
4626
+ Their transaction must serialize `isPurged`, head/active-run checks, `remove`, and **all** mutations
4627
+ for the same ID, including absent-ID admission. An optimistic head CAS alone is insufficient. `remove`
4628
+ must delete every owned payload/index and preserve the tombstone; errors must roll back the entire
4629
+ transaction. Drivers without this guarantee must leave the capability absent. See ADR 0138.
4630
+
4551
4631
  ## Events and reconnect
4552
4632
 
4553
4633
  `publish` receives event classes with different guarantees:
@@ -4752,6 +4832,40 @@ replace a leading summary on the next compaction.
4752
4832
  Set `maxAttempts` to allow bounded conflict recovery. Every retry reloads the snapshot, reselects the
4753
4833
  eligible range and recomputes the summary; the stale summary is never retried.
4754
4834
 
4835
+ ### Compacting without the store
4836
+
4837
+ The *selection* inside compaction needs no store, no version and no runtime, so
4838
+ it is published on its own as `selectCompactableHistory` — for an application
4839
+ that drives the model itself and keeps its own history (→ ADR 0142):
4840
+
4841
+ ```ts
4842
+ import { selectCompactableHistory } from 'stitchkit/agent-runtime'
4843
+
4844
+ const { leadingSummary, compactable, retained } = selectCompactableHistory({
4845
+ messages,
4846
+ keepRecentTurns: 3,
4847
+ })
4848
+ if (compactable.length > 0) {
4849
+ const summary = await summarize(compactable, leadingSummary)
4850
+ messages = [summary, ...retained] // replace the old summary, never stack one
4851
+ }
4852
+ ```
4853
+
4854
+ `compactable` is the oldest **whole complete** turns and `retained` is
4855
+ everything the model must still hear; together they are the input, in order. A
4856
+ turn holding a tool call whose result never arrived is never eligible, and
4857
+ neither is anything after it: half a turn hands the provider a call with no
4858
+ result, which most of them refuse outright.
4859
+
4860
+ `structuredCompaction` calls this same function, so the published selection and
4861
+ the runtime's cannot drift into two behaviours. What it adds is the part that
4862
+ does need the store — writing the result back under a version check.
4863
+
4864
+ Deciding *when* to compact is yours: `AgentRuntimeRunContext.contextUsage`
4865
+ carries the last step's prompt size beside the model's window, and dividing them
4866
+ is one line where the threshold is decided. The core does not own that ratio, on
4867
+ purpose — see ADR 0142 for what it publishes and what it declines to.
4868
+
4755
4869
  ## Observability
4756
4870
 
4757
4871
  `createAgentObservability` emits a separate operator-only `AgentRunEvent`. It
@@ -7390,6 +7504,68 @@ const payload = await verifyJwt(token, env.JWT_SECRET)
7390
7504
  `extractToken(req, cookieName?)` reads a bearer token from the `Authorization`
7391
7505
  header, or from the named cookie.
7392
7506
 
7507
+ ## Telegram Mini Apps
7508
+
7509
+ A Mini App hands its backend an `initData` query string that Telegram signed
7510
+ with a key derived from the bot token. `verifyTelegramInitData` checks that
7511
+ signature and reports what the string says — and refuses with a **reason**,
7512
+ because an application answers a stale string differently from a forged one.
7513
+
7514
+ ```ts
7515
+ import { verifyTelegramInitData } from 'stitchkit/telegram'
7516
+ import { unauthorized } from 'stitchkit/contract'
7517
+ import { env } from './env'
7518
+
7519
+ const result = await verifyTelegramInitData({
7520
+ initData,
7521
+ botToken: env.BOT_TOKEN,
7522
+ maxAgeSeconds: 3600, // a signed string is otherwise valid forever
7523
+ })
7524
+ if (!result.valid) {
7525
+ // 'missing-hash' | 'signature-mismatch' | 'malformed' | 'expired'
7526
+ throw unauthorized(result.reason === 'expired' ? 'Reopen the app' : 'Invalid session')
7527
+ }
7528
+ const telegramId = result.data.user?.id
7529
+ ```
7530
+
7531
+ Three things it does that a hand-written check usually does not:
7532
+
7533
+ - the **signature is checked first**, before `auth_date` or anything else is
7534
+ read — an expiry inside an unverified payload is a number the sender chose;
7535
+ - digests are compared **without an early exit**, so the time a rejection takes
7536
+ is not a measurement of how much of the digest was right;
7537
+ - `raw` keeps every signed pair, so a field Telegram adds after this release
7538
+ does not need a release to reach.
7539
+
7540
+ `maxAgeSeconds` is optional and there is no default: only the application knows
7541
+ how long its own session is worth. Omitting it means the string never expires,
7542
+ which is a decision rather than an oversight.
7543
+
7544
+ The module is server-only and pulls in no bot library — the token never belongs
7545
+ in a browser bundle. `stitchkit/application/grammy` remains the lifecycle
7546
+ adapter for an injected grammY bot. Its companion,
7547
+ `classifyTelegramSendFailure`, names why a send was refused and separates *retry
7548
+ this send* from *stop addressing this recipient*:
7549
+
7550
+ ```ts
7551
+ import { classifyTelegramSendFailure } from 'stitchkit/telegram'
7552
+
7553
+ try {
7554
+ await bot.api.sendMessage(chatId, text)
7555
+ } catch (error) {
7556
+ const failure = classifyTelegramSendFailure(error)
7557
+ if (failure.recipientUnreachable) await markUnreachable(chatId, failure.reason)
7558
+ else if (failure.retryable) await requeue(chatId, failure.retryAfterSeconds)
7559
+ }
7560
+ ```
7561
+
7562
+ The two flags are separate because one answer cannot serve both. A rate limit is
7563
+ retryable and implicates nobody; a blocked user is unreachable and no retry
7564
+ helps; a message Telegram could not parse is *neither* — the recipient is fine
7565
+ and our payload is wrong, which is the case a list of substrings quietly counts
7566
+ against the user. An unrecognised refusal leaves the recipient reachable: losing
7567
+ a working subscriber forever costs more than one wasted send.
7568
+
7393
7569
  ## Cookies
7394
7570
 
7395
7571
  ```ts
@@ -9117,6 +9293,94 @@ additive** — adopting it changes nothing in your code. (See
9117
9293
  So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
9118
9294
  current one *up to* your target, and apply each snippet.
9119
9295
 
9296
+ ## Released migration: 0.71.0
9297
+
9298
+ The Agent coding tools. Two of the three changes are visible to the compiler;
9299
+ the third is the one to read carefully, because nothing will point at it.
9300
+
9301
+ ### If you implement `authorize`
9302
+
9303
+ The operation union gained `edit`, `list` and `glob` and lost `patch`. An
9304
+ exhaustive matcher stops compiling and the compiler shows you every arm. **A
9305
+ matcher with a default branch does not**, and that is the dangerous case:
9306
+
9307
+ ```ts
9308
+ // before — and after this upgrade, silently wrong in both directions
9309
+ if (request.operation === 'patch') return reviewPatch(request)
9310
+ return true // now also authorizes edit, list and glob
9311
+ // …or: return false // now also kills edit_file
9312
+ ```
9313
+
9314
+ ```ts
9315
+ // after
9316
+ switch (request.operation) {
9317
+ case 'edit': return reviewEdit(request) // the old `patch` payload, unchanged
9318
+ case 'list':
9319
+ case 'glob': return true // or your own policy
9320
+ // …existing read / write / search / shell / artifact-read arms
9321
+ }
9322
+ ```
9323
+
9324
+ `write` gained `createsDirectories` — the workspace-relative directories the
9325
+ call would create, outermost first, reported **before** anything is created. A
9326
+ host that wants to refuse implicit directory creation now can.
9327
+
9328
+ ### If you call `apply_patch`
9329
+
9330
+ It is `edit_file`, and it is one call:
9331
+
9332
+ ```ts
9333
+ // before
9334
+ const read = await readFile({ path })
9335
+ await applyPatch({ path, baseSha256: read.sha256, oldText, newText, dryRun: true })
9336
+ await applyPatch({ path, baseSha256: read.sha256, oldText, newText, dryRun: false })
9337
+
9338
+ // after
9339
+ await editFile({ path, oldText, newText })
9340
+ ```
9341
+
9342
+ `expectedSha256` is optional and still refuses a stale base with `CONFLICT` when
9343
+ you pass it; `edit_file` returns the resulting `sha256`, so a chain of edits
9344
+ never needs to re-read. If you key an approval policy or a UI label on the string
9345
+ `apply_patch`, update the key — nothing will fail loudly.
9346
+
9347
+ ### If you match on `INTERNAL_SERVER_ERROR` from a coding tool
9348
+
9349
+ Ordinary outcomes no longer arrive that way. A missing file is `NOT_FOUND`, an
9350
+ existing file without `overwrite` is `CONFLICT`, an ambiguous snippet is
9351
+ `CONFLICT` with the occurrence count, a path outside the root is `FORBIDDEN`.
9352
+ Code that treated any coding-tool failure as an internal fault will now see
9353
+ codes it did not before; code that showed the model an empty error now has a
9354
+ sentence and a `hint` to show it. Host-level causes are unchanged and still
9355
+ scrubbed.
9356
+
9357
+ ### If you write files into new directories
9358
+
9359
+ Nothing to change: `write_file` creates missing parents inside the root. Read
9360
+ `createdDirectories` in the result if you want to notice a typo — a path that
9361
+ was a failure before is now a successful write into a new tree.
9362
+
9363
+ ### If you want a step to know its context budget
9364
+
9365
+ Opt in by reading it; nothing is injected for you:
9366
+
9367
+ ```ts
9368
+ loop: {
9369
+ prepareStep: ({ contextUsage }) => {
9370
+ const used = contextUsage?.usedTokens
9371
+ if (used?.provenance === 'unavailable') return {} // no step has landed yet
9372
+ const fraction = (used?.value ?? 0) / (contextUsage?.contextWindow ?? 1)
9373
+ // …render it wherever you put it
9374
+ return {}
9375
+ },
9376
+ }
9377
+ ```
9378
+
9379
+ Put it at the **tail** of the conversation rather than in the system
9380
+ instructions unless you have a reason: changing the system prompt on every step
9381
+ invalidates the provider's prefix cache for the whole conversation, and on a long
9382
+ run that is a multiple of the input cost.
9383
+
9120
9384
  ## Released migration: 0.70.0
9121
9385
 
9122
9386
  ### Descriptor-backed Agent filesystem containment
@@ -12088,6 +12352,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
12088
12352
  | `generateTraceId` | function | a fresh trace id |
12089
12353
  | `resolveTraceId` | function | the default per-request trace-id resolver |
12090
12354
  | `extractIp` | function | the caller IP from a request |
12355
+ | `isPublicIp` | function | whether an address belongs to the public internet; anything unparseable is not |
12091
12356
  | `resolveSocketIp` | function | the caller IP for a Socket.IO handshake (proxy-aware) |
12092
12357
  | `getClientInfo` | function | caller IP + user-agent |
12093
12358
  | `EventBus` | _type_ | the `createEventBus` handle |
@@ -12318,6 +12583,11 @@ Server-only optional application runtime. See the
12318
12583
  | `AgentHistoryMutationSchema` | schema | typed canonical message mutation applied inside the winning state transaction |
12319
12584
  | `RecoverAgentRunSchema` | schema | explicit abandon/requeue recovery decision; acquired runs require replay-safe evidence |
12320
12585
  | `createMemoryAgentRuntimeStore` | function | process-local reference adapter, not production durability |
12586
+ | `purgeAgentConversation` | function | dispatch optional atomic deletion; returns `unsupported`, `active`, `conflict`, `purged` or `already_purged` |
12587
+ | `AgentConversationPurgeInputSchema` / `AgentConversationPurgeInput` | schema / _type_ | conversation ID and optional expected snapshot version |
12588
+ | `AgentConversationPurgeResultSchema` / `AgentConversationPurgeResult` | schema / _type_ | typed deletion/refusal outcomes; active refusal includes run IDs |
12589
+ | `AgentConversationPurgedError` | class | runtime mutation rejected because its conversation ID is permanently purged |
12590
+ | `AgentConversationPurgeDriver` | _type_ | optional `driver.conversations` capability: serialized tombstone read and atomic removal of all owned records |
12321
12591
  | `projectAgentHistory` | function | asynchronously project canonical records and resolved multimodal files into provider-valid AI SDK messages |
12322
12592
  | `defineModelRegistry` | function | typed language-model descriptors, capabilities and provider construction |
12323
12593
  | `AgentModelCatalogSchema` / `AgentModelCatalog` | schema / _type_ | provider-neutral complete/partial model catalog with separately sourced popularity, metrics, prices and observation time |
@@ -12337,6 +12607,8 @@ Server-only optional application runtime. See the
12337
12607
  | `AgentConversationMessagePageSchema` / `AgentConversationMessagePage` | schema / _type_ | cursor-paged durable message history |
12338
12608
  | `composeAgentPrompt` | function | ordered prompt contributions and provenance-aware signed context budget; irreducible reservation deficits are `oversized`, not compactable history |
12339
12609
  | `structuredCompaction` | function | summarize a provider-valid snapshot range and replace it through CAS |
12610
+ | `selectCompactableHistory` | function | which oldest whole complete turns may be summarised away — the half of compaction that needs no store (→ ADR 0142) |
12611
+ | `SelectCompactableHistoryOptions` / `CompactableHistory` | _type_ | message list, retained-turn count and evidence policy in; `leadingSummary`, `compactable` and `retained` out |
12340
12612
  | `createAgentSessionCoordinator` | function | strict process-local queue/interrupt/supersede lifecycle |
12341
12613
  | `AgentRuntimeStopPolicy` | _type_ | named custom AI SDK stop condition persisted and published on policy stop |
12342
12614
  | `AgentRuntimePrepareStep` | _type_ | per-run controlled step callback with typed domain context and managed run signal/fence |
@@ -12365,6 +12637,29 @@ transport adapters validate the same records. Runtime composition types are `Age
12365
12637
  `AgentSessionCoordinator`, `AgentCompactionContext`, `AgentCompactionResult` and
12366
12638
  `StructuredCompactionConfig`.
12367
12639
 
12640
+ A provider refusal is classified rather than phrased. `classifyProviderFailure` returns an
12641
+ `AgentProviderFailure` — an `AgentProviderFailureReason` (`insufficient-credits`, `rate-limited`,
12642
+ `model-unavailable`, `context-overflow`, `timeout`, `cancelled`, `unknown`), the provider's `status`
12643
+ when it supplied one, whether the same request is `retryable` unchanged, and the `evidence` the
12644
+ answer rests on: `status` is the provider stating its own answer, `message` is us reading its prose,
12645
+ and `none` is an honest refusal to guess. The sentence a user reads stays with the application —
12646
+ its tone and its decision about what to admit are not the core's to make. `isToolResultFailure`
12647
+ recognises a failure carried inside a *successful* tool result, in both the bare and the
12648
+ `{ value: … }` envelope. Both are plain functions and need no runtime. → ADR 0141
12649
+
12650
+ `normalizeOpenRouterUsage` is the same normalisation `openRouterProvider`
12651
+ applies, exported so an application calling the SDK directly gets provenance-correct numbers
12652
+ without adopting the runtime.
12653
+
12654
+ `AgentContextUsage` reaches every step through `AgentRuntimeRunContext.contextUsage`: how full the
12655
+ model's context is, as `usedTokens` (an `AgentUsageValue`, so it carries the provenance that says
12656
+ where the number came from) beside the model's declared `contextWindow`. It is the **last completed
12657
+ step's prompt size**, not the run's cumulative input tokens — cumulative counts every step's prompt
12658
+ again and is a multiple of the real fill. Before the first step lands there is no provider-reported
12659
+ number and the provenance is `unavailable`, which is a different fact from zero. No fraction is
12660
+ exposed: dividing is one line where it is rendered, and the output reserve belongs to the
12661
+ consumer's prompt budget rather than to this layer.
12662
+
12368
12663
  Canonical protocol exports are `AgentProtocol`, `AgentProtocolConfig`, `AgentTerminalAcceptance`,
12369
12664
  `AgentTerminalAcceptanceInput`, `hasAgentTerminalOutput`, `AgentRecordIdSchema`, `AgentRecordVersionSchema`,
12370
12665
  `AgentTimestampSchema`, `AgentJsonObjectSchema`, `AgentProviderEnvelopeSchema`,
@@ -12419,6 +12714,12 @@ neither grows with the length of the conversation, and neither needs anything ne
12419
12714
  conversation — that is what the store's reducer validates against, and what the runtime builds a
12420
12715
  prompt from (→ ADR 0112).
12421
12716
 
12717
+ `AgentRuntimeStore.purgeConversation` is optional; `purgeAgentConversation(store, input)` explicitly
12718
+ handles unsupported stores. Official memory and initialized SQLite stores refuse active runs,
12719
+ remove every owned payload and reserve the conversation ID permanently. Empty snapshots are not
12720
+ deletion receipts. Consumer metadata, files, event logs and UI cache invalidation remain outside
12721
+ the transaction. See [purging a conversation](../guide/agent-runtime.md#purging-a-conversation).
12722
+
12422
12723
  Store command/result exports are `AcceptInputAndAssignRun`, `AcceptInputAndAssignRunSchema`,
12423
12724
  `AcquireAgentRun`, `AcquireAgentRunSchema`, `CheckpointRunAssistant`,
12424
12725
  `CheckpointRunAssistantSchema`, `CommitRunTerminal`, `CommitRunTerminalSchema`,
@@ -12500,12 +12801,14 @@ explicit policy; use the Bun or Node SQLite leaf for durable reopen.
12500
12801
  ## `stitchkit/agent-runtime/coding-tools`
12501
12802
 
12502
12803
  Server-only evolving, peer-free direct runtime tools. `createAgentCodingTools(config)` returns
12503
- `read_file`, `write_file`, `search_files`, `apply_patch`, optional `run_command` and, when an
12804
+ `read_file`, `write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, optional
12805
+ `run_command` and, when an
12504
12806
  artifact store is supplied, `read_output`.
12505
12807
 
12506
12808
  | Export | Kind | Summary |
12507
12809
  |--------|------|---------|
12508
- | `createAgentCodingTools` | function | construct direct host-authorized bounded file, search, guarded patch, shell and artifact runtime-tool definitions; filesystem operations use Linux `/proc/self/fd` or the packaged macOS Node-API backend and otherwise fail closed |
12810
+ | `createAgentCodingTools` | function | construct direct host-authorized bounded file, listing, glob, search, exact-snippet edit, shell and artifact runtime-tool definitions; every ordinary refusal is a typed code with an instructive `hint`, and filesystem operations use Linux `/proc/self/fd` or the packaged macOS Node-API backend and otherwise fail closed |
12811
+ | `AGENT_CODING_TOOL_NAMES` | const | the mounted tool names — `read_file`, `write_file`, `edit_file`, `list_directory`, `glob`, `search_files`, `run_command`, `read_output` |
12509
12812
  | `AgentCodingToolDefinition` | _type_ | peer-free structural direct-tool shape accepted by the canonical runtime-tool surface |
12510
12813
  | `AgentCodingToolConfig` | _type_ | absolute root, required authorization callback, finite executable alias map, exact child environment and optional limits |
12511
12814
  | `AgentCodingToolAuthorizationSchema` / `AgentCodingToolAuthorization` | schema / _type_ | discriminated read/write/search/patch/shell/artifact decision presented to host policy before effect |
@@ -13028,6 +13331,26 @@ available from `stitchkit/contract`.
13028
13331
 
13029
13332
  ---
13030
13333
 
13334
+ ## `stitchkit/telegram`
13335
+
13336
+ Peer-free server-only Telegram platform primitives. Importing this resolves no
13337
+ bot library; `stitchkit/application/grammy` remains the lifecycle adapter for an
13338
+ injected grammY bot. → ADR 0143
13339
+
13340
+ | Export | Kind | Summary |
13341
+ |--------|------|---------|
13342
+ | `verifyTelegramInitData` | function | verify a Mini App `initData` signature against the bot token in constant time, before reading anything out of it |
13343
+ | `VerifyTelegramInitDataOptions` | _type_ | raw `initData`, bot token, optional `maxAgeSeconds` bound and injected clock |
13344
+ | `TelegramInitDataVerification` | _type_ | `{ valid: true, data }` or a refusal carrying its reason |
13345
+ | `TelegramInitData` | _type_ | verified `user`/`receiver`, `authDate`, `ageSeconds`, `queryId`, `startParam`, `chatType`, `chatInstance` and every signed pair in `raw` |
13346
+ | `TelegramInitDataUser` | _type_ | camelCase user record inferred from Telegram's signed `user` payload |
13347
+ | `TelegramInitDataRefusal` | _type_ | `missing-hash` / `signature-mismatch` / `malformed` / `expired` — an expired string is not a forged one |
13348
+ | `classifyTelegramSendFailure` | function | name a refused Bot API send and separate "retry this send" from "stop addressing this recipient" |
13349
+ | `TelegramSendFailure` | _type_ | reason, `status`, Telegram-stated `retryAfterSeconds`, `retryable`, `recipientUnreachable` and which evidence produced the answer |
13350
+ | `TelegramSendFailureReason` | _type_ | `blocked-by-user` / `user-deactivated` / `chat-not-found` / `not-started` / `rate-limited` / `message-invalid` / `server-error` / `unknown` |
13351
+
13352
+ ---
13353
+
13031
13354
  ## `stitchkit/declaration`
13032
13355
 
13033
13356
  Zod-only, dependency-free. The **project declaration**: the single
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.70.5",
3
+ "version": "0.71.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",
@@ -129,6 +129,10 @@
129
129
  "types": "./dist/files.d.ts",
130
130
  "import": "./dist/files.js"
131
131
  },
132
+ "./telegram": {
133
+ "types": "./dist/telegram.d.ts",
134
+ "import": "./dist/telegram.js"
135
+ },
132
136
  "./declaration": {
133
137
  "types": "./dist/declaration.d.ts",
134
138
  "import": "./dist/declaration.js"
@@ -145,7 +149,7 @@
145
149
  "build:native-contained-files": "node scripts/build-contained-files-native.mjs",
146
150
  "check": "bun x tsc --noEmit",
147
151
  "build:browser": "bun build src/index.ts src/react.ts src/contract/index.ts src/declaration.ts src/agent-runtime-browser.ts --outdir dist --target node --packages external --splitting --root src",
148
- "build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/tool-invoker.ts src/cli.ts src/remote.ts src/files.ts src/testing.ts src/observability/index.ts src/agent-runtime.ts src/agent-runtime-harness.ts src/agent-runtime-coding-tools.ts src/agent-runtime-openrouter.ts src/agent-runtime-sqlite-bun.ts src/agent-runtime-sqlite-node.ts src/application.ts src/application-grammy.ts src/application-opentelemetry.ts --outdir dist --target node --packages external --splitting --root src",
152
+ "build:server": "bun build src/server/index.ts src/node.ts src/tools.ts src/tool-invoker.ts src/cli.ts src/remote.ts src/files.ts src/testing.ts src/observability/index.ts src/agent-runtime.ts src/agent-runtime-harness.ts src/agent-runtime-coding-tools.ts src/agent-runtime-openrouter.ts src/agent-runtime-sqlite-bun.ts src/agent-runtime-sqlite-node.ts src/application.ts src/application-grammy.ts src/application-opentelemetry.ts src/telegram.ts --outdir dist --target node --packages external --splitting --root src",
149
153
  "build:js": "bun run build:browser && bun run build:server && bun scripts/preserve-webpack-ignore.mjs",
150
154
  "build:types": "bun x tsc -p tsconfig.build.json --emitDeclarationOnly && bun scripts/rewrite-declaration-specifiers.mjs",
151
155
  "build": "rm -rf dist && bun run build:js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs",