stitchkit 0.104.1 → 0.105.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 +381 -12
- package/darwin-native.cjs +4 -2
- package/dist/agent-runtime/browser-control-contract.d.ts +32 -2
- package/dist/agent-runtime/browser-control-contract.d.ts.map +1 -1
- package/dist/agent-runtime/browser-controller.d.ts +21 -3
- package/dist/agent-runtime/browser-controller.d.ts.map +1 -1
- package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-refusals.d.ts +1 -0
- package/dist/agent-runtime/coding-tool-refusals.d.ts.map +1 -1
- package/dist/agent-runtime/coding-tool-shell.d.ts.map +1 -1
- package/dist/agent-runtime/compaction.d.ts.map +1 -1
- package/dist/agent-runtime/contained-write.d.ts.map +1 -1
- package/dist/agent-runtime/control-schema.d.ts +48 -1
- package/dist/agent-runtime/control-schema.d.ts.map +1 -1
- package/dist/agent-runtime/deferred-tool-search.d.ts +3 -2
- package/dist/agent-runtime/deferred-tool-search.d.ts.map +1 -1
- package/dist/agent-runtime/harness-control.d.ts.map +1 -1
- package/dist/agent-runtime/harness-realtime.d.ts +34 -4
- package/dist/agent-runtime/harness-realtime.d.ts.map +1 -1
- package/dist/agent-runtime/runtime-recovery.d.ts.map +1 -1
- package/dist/agent-runtime/runtime-state.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox-contract.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox-process-owner.d.ts +9 -1
- package/dist/agent-runtime/sandbox-process-owner.d.ts.map +1 -1
- package/dist/agent-runtime/sandbox.d.ts +32 -1
- package/dist/agent-runtime/sandbox.d.ts.map +1 -1
- package/dist/agent-runtime/schedule-records.d.ts.map +1 -1
- package/dist/agent-runtime/schedules.d.ts.map +1 -1
- package/dist/agent-runtime/store-events.d.ts +9 -4
- package/dist/agent-runtime/store-events.d.ts.map +1 -1
- package/dist/agent-runtime/testing.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-contract.d.ts +50 -0
- package/dist/application/diagnostic-journal-contract.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-generations.d.ts +24 -0
- package/dist/application/diagnostic-journal-generations.d.ts.map +1 -0
- package/dist/application/diagnostic-journal-manager.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-open.d.ts +10 -0
- package/dist/application/diagnostic-journal-open.d.ts.map +1 -0
- package/dist/application/diagnostic-journal-quarantine.d.ts +28 -0
- package/dist/application/diagnostic-journal-quarantine.d.ts.map +1 -0
- package/dist/application/diagnostic-journal-read-contract.d.ts +73 -3
- package/dist/application/diagnostic-journal-read-contract.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-reader.d.ts +17 -2
- package/dist/application/diagnostic-journal-reader.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-recovery.d.ts +18 -2
- package/dist/application/diagnostic-journal-recovery.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-storage.d.ts +6 -2
- package/dist/application/diagnostic-journal-storage.d.ts.map +1 -1
- package/dist/application/diagnostic-journal-tail.d.ts +12 -0
- package/dist/application/diagnostic-journal-tail.d.ts.map +1 -0
- package/dist/application/diagnostic-journal.d.ts +3 -0
- package/dist/application/diagnostic-journal.d.ts.map +1 -1
- package/dist/application/directory-inbox-accept.d.ts +3 -3
- package/dist/application/directory-inbox-accept.d.ts.map +1 -1
- package/dist/application/directory-inbox-contract.d.ts +38 -10
- package/dist/application/directory-inbox-contract.d.ts.map +1 -1
- package/dist/application/directory-inbox-delivery.d.ts +3 -3
- package/dist/application/directory-inbox-delivery.d.ts.map +1 -1
- package/dist/application/directory-inbox-files.d.ts +17 -2
- package/dist/application/directory-inbox-files.d.ts.map +1 -1
- package/dist/application/directory-inbox-state.d.ts +1 -1
- package/dist/application/directory-inbox-state.d.ts.map +1 -1
- package/dist/application/directory-inbox.d.ts +1 -1
- package/dist/application/directory-inbox.d.ts.map +1 -1
- package/dist/application/file-state-store.d.ts +21 -1
- package/dist/application/file-state-store.d.ts.map +1 -1
- package/dist/application/grammy.d.ts.map +1 -1
- package/dist/application/kernel-contract.d.ts.map +1 -1
- package/dist/application/revision-signal.d.ts.map +1 -1
- package/dist/application/state-store.d.ts +3 -0
- package/dist/application/state-store.d.ts.map +1 -1
- package/dist/browser/api-error.d.ts +38 -0
- package/dist/browser/api-error.d.ts.map +1 -0
- package/dist/browser/chunked-upload.d.ts.map +1 -1
- package/dist/browser/client-executors.d.ts.map +1 -1
- package/dist/browser/client-url.d.ts.map +1 -1
- package/dist/browser/contract-stream.d.ts.map +1 -1
- package/dist/browser/http.d.ts +0 -25
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/resumable.d.ts.map +1 -1
- package/dist/contract/define-validation.d.ts +0 -1
- package/dist/contract/define-validation.d.ts.map +1 -1
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/errors-factory.d.ts.map +1 -1
- package/dist/contract/errors.d.ts +15 -18
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/normalize.d.ts.map +1 -1
- package/dist/durability/effect.d.ts.map +1 -1
- package/dist/durability/scheduler.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/browser.js +29 -18
- package/dist/entrypoints/agent-runtime/coding-tools.js +8 -7
- package/dist/entrypoints/agent-runtime/harness.js +15 -14
- package/dist/entrypoints/agent-runtime/realtime.d.ts +1 -1
- package/dist/entrypoints/agent-runtime/realtime.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/realtime.js +119 -54
- package/dist/entrypoints/agent-runtime/sandbox.js +23 -17
- package/dist/entrypoints/agent-runtime/sqlite/bun.js +4 -3
- package/dist/entrypoints/agent-runtime/sqlite/node.js +4 -3
- package/dist/entrypoints/agent-runtime/testing.js +8 -6
- package/dist/entrypoints/agent-runtime.js +37 -38
- package/dist/entrypoints/application/diagnostic-journal.js +305 -111
- package/dist/entrypoints/application/directory-inbox.js +174 -77
- package/dist/entrypoints/application/grammy.js +5 -2
- package/dist/entrypoints/application/opentelemetry.js +2 -2
- package/dist/entrypoints/application.d.ts +2 -2
- package/dist/entrypoints/application.d.ts.map +1 -1
- package/dist/entrypoints/application.js +17 -8
- package/dist/entrypoints/cli/publish.d.ts +16 -0
- package/dist/entrypoints/cli/publish.d.ts.map +1 -0
- package/dist/entrypoints/cli/publish.js +527 -0
- package/dist/entrypoints/cli.d.ts +1 -2
- package/dist/entrypoints/cli.d.ts.map +1 -1
- package/dist/entrypoints/cli.js +59 -966
- package/dist/entrypoints/contract.js +3 -3
- package/dist/entrypoints/files/packaging.js +196 -56
- package/dist/entrypoints/files.d.ts +1 -0
- package/dist/entrypoints/files.d.ts.map +1 -1
- package/dist/entrypoints/files.js +17 -10
- package/dist/entrypoints/index.d.ts +2 -1
- package/dist/entrypoints/index.d.ts.map +1 -1
- package/dist/entrypoints/index.js +31 -60
- package/dist/entrypoints/node.js +8 -8
- package/dist/entrypoints/observability.js +3 -3
- package/dist/entrypoints/primitives.js +67 -83
- package/dist/entrypoints/process.d.ts +1 -1
- package/dist/entrypoints/process.d.ts.map +1 -1
- package/dist/entrypoints/process.js +2 -2
- package/dist/entrypoints/react.js +4 -2
- package/dist/entrypoints/remote.js +37 -13
- package/dist/entrypoints/server.js +18 -16
- package/dist/entrypoints/telegram/screens.js +5 -3
- package/dist/entrypoints/telegram.js +31 -46
- package/dist/entrypoints/testing.d.ts +1 -1
- package/dist/entrypoints/testing.d.ts.map +1 -1
- package/dist/entrypoints/testing.js +444 -448
- package/dist/entrypoints/tools/connections.js +268 -105
- package/dist/entrypoints/tools/contract.js +2 -2
- package/dist/entrypoints/tools/invoker.js +8 -7
- package/dist/entrypoints/tools/mcp.d.ts +2 -2
- package/dist/entrypoints/tools/mcp.d.ts.map +1 -1
- package/dist/entrypoints/tools/mcp.js +10 -9
- package/dist/entrypoints/tools.d.ts +0 -1
- package/dist/entrypoints/tools.d.ts.map +1 -1
- package/dist/entrypoints/tools.js +118 -53
- package/dist/entrypoints/tracking.js +2 -3
- package/dist/files/boundary.d.ts +2 -2
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files/chunk-spool.d.ts.map +1 -1
- package/dist/files/file-io.d.ts +2 -2
- package/dist/files/file-io.d.ts.map +1 -1
- package/dist/files/native-packaging-layout.d.ts +50 -4
- package/dist/files/native-packaging-layout.d.ts.map +1 -1
- package/dist/files/native-packaging.d.ts +104 -21
- package/dist/files/native-packaging.d.ts.map +1 -1
- package/dist/{index-r5hsgkz5.js → index-0gs4ymfj.js} +6 -3
- package/dist/{index-zax7resm.js → index-0gxvns6s.js} +1 -13
- package/dist/index-0s4xebbg.js +8 -0
- package/dist/{index-r4r70zmh.js → index-12zsej7b.js} +5 -2
- package/dist/{index-kemm0ej5.js → index-1pwwan01.js} +65 -22
- package/dist/{index-vgkbcnkc.js → index-299sm5tr.js} +1 -1
- package/dist/{index-2vnh05w4.js → index-2k91mewf.js} +31 -74
- package/dist/{index-269zqj8m.js → index-2rc6jd9n.js} +28 -12
- package/dist/{index-wh0ecvyk.js → index-37asdw3a.js} +1 -1
- package/dist/index-3h0ztg0z.js +285 -0
- package/dist/{index-g95fk660.js → index-4vsby8n8.js} +8 -15
- package/dist/index-556g5hsh.js +17 -0
- package/dist/{index-adhgd4pe.js → index-5p55xreg.js} +2 -2
- package/dist/index-5w6t91ze.js +47 -0
- package/dist/{index-9pw0a51a.js → index-6yxhe58g.js} +40 -7
- package/dist/{index-6d9xaxw1.js → index-8ks9b85e.js} +33 -5
- package/dist/{index-bhb950qs.js → index-916yh4e2.js} +25 -12
- package/dist/index-9tqbxv5p.js +44 -0
- package/dist/{index-5csmzesf.js → index-azx12jgv.js} +4 -3
- package/dist/{index-mbnnw3f2.js → index-b0kddvzw.js} +15 -5
- package/dist/{index-n5yqxd3d.js → index-b0w7rbpt.js} +23 -2
- package/dist/index-b56nnscm.js +132 -0
- package/dist/{index-0ndk4555.js → index-c0hffcmj.js} +2 -2
- package/dist/{index-pptyv13k.js → index-c17xcx4k.js} +42 -6
- package/dist/{index-g2j7pnak.js → index-cmzvhhtd.js} +5 -2
- package/dist/index-cq59agna.js +51 -0
- package/dist/{index-ssmd41kh.js → index-d2h99n7d.js} +1 -1
- package/dist/{index-z94wht4y.js → index-dky0dcmf.js} +34 -5
- package/dist/{index-za6j7nwy.js → index-dsnjzb62.js} +104 -51
- package/dist/index-eemvahkc.js +21 -0
- package/dist/index-ef8gdaq0.js +105 -0
- package/dist/{index-fa4wg312.js → index-f199nfq5.js} +10 -6
- package/dist/{index-ck71tbcf.js → index-fsw1ybf3.js} +11 -15
- package/dist/{index-f330qqyg.js → index-g2vfg7ak.js} +217 -63
- package/dist/{index-fk18k56k.js → index-gr5g3xfw.js} +11 -18
- package/dist/{index-jn9g8xdt.js → index-hjvje0pf.js} +6 -4
- package/dist/{index-x928baz2.js → index-j9gxbztg.js} +18 -14
- package/dist/index-kfjkb396.js +37 -0
- package/dist/{index-6drst9sg.js → index-ksfagmyz.js} +45 -345
- package/dist/index-m616f0yz.js +120 -0
- package/dist/{index-fm1c1a08.js → index-m6w6jtda.js} +40 -21
- package/dist/{index-ptn0g0gj.js → index-ma963wz7.js} +21 -20
- package/dist/{index-bakgedh8.js → index-msfpyy2f.js} +2 -4
- package/dist/{index-ywppsaby.js → index-n1nszyk0.js} +23 -34
- package/dist/{index-v14qqmys.js → index-n4689krb.js} +1 -1
- package/dist/{index-a0zzh8ak.js → index-pz3y6b6g.js} +3 -3
- package/dist/{index-43p08g7t.js → index-pzb63z66.js} +12 -8
- package/dist/{index-h97tqqp1.js → index-qaet40b2.js} +5 -3
- package/dist/{index-t1sr4aks.js → index-qaqbdm7s.js} +52 -6
- package/dist/{index-77nxk6ay.js → index-r393tnfp.js} +16 -10
- package/dist/{index-kb8styz3.js → index-rpzewktb.js} +15 -5
- package/dist/{index-27a2pj09.js → index-sj28htvf.js} +1 -1
- package/dist/index-szybbezc.js +455 -0
- package/dist/{index-2hyavjjj.js → index-wbhdzxar.js} +3 -3
- package/dist/{index-4e0ah203.js → index-wdy68sh6.js} +47 -24
- package/dist/{index-hbd32c6a.js → index-wqmvtnkp.js} +38 -89
- package/dist/{index-hp9d77wk.js → index-x7w1z6pf.js} +15 -4
- package/dist/{index-20yvtn9a.js → index-yt7gyg5x.js} +1 -1
- package/dist/{index-cvda079a.js → index-yvfk3y36.js} +12 -2
- package/dist/{index-5z6jn897.js → index-z23rfxhj.js} +1 -1
- package/dist/internal/abort-race.d.ts +8 -2
- package/dist/internal/abort-race.d.ts.map +1 -1
- package/dist/internal/atomic-file.d.ts +55 -9
- package/dist/internal/atomic-file.d.ts.map +1 -1
- package/dist/internal/atomic-publication.d.ts +39 -4
- package/dist/internal/atomic-publication.d.ts.map +1 -1
- package/dist/internal/atomic-staging.d.ts +46 -0
- package/dist/internal/atomic-staging.d.ts.map +1 -0
- package/dist/internal/bounded-file-read.d.ts +19 -11
- package/dist/internal/bounded-file-read.d.ts.map +1 -1
- package/dist/internal/bounded-lines.d.ts.map +1 -1
- package/dist/internal/close-after.d.ts +11 -0
- package/dist/internal/close-after.d.ts.map +1 -0
- package/dist/internal/deadline.d.ts +10 -0
- package/dist/internal/deadline.d.ts.map +1 -1
- package/dist/internal/exclusive-lock-read.d.ts +10 -12
- package/dist/internal/exclusive-lock-read.d.ts.map +1 -1
- package/dist/internal/exclusive-lock.d.ts +2 -2
- package/dist/internal/exclusive-lock.d.ts.map +1 -1
- package/dist/internal/file-observation.d.ts +14 -0
- package/dist/internal/file-observation.d.ts.map +1 -0
- package/dist/internal/json-data.d.ts +22 -5
- package/dist/internal/json-data.d.ts.map +1 -1
- package/dist/internal/positive-integer.d.ts +9 -0
- package/dist/internal/positive-integer.d.ts.map +1 -0
- package/dist/internal/process-instance.d.ts +35 -5
- package/dist/internal/process-instance.d.ts.map +1 -1
- package/dist/internal/secure-fetch.d.ts.map +1 -1
- package/dist/internal/timers.d.ts +11 -0
- package/dist/internal/timers.d.ts.map +1 -0
- package/dist/internal/with-exclusive-lock.d.ts +5 -4
- package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
- package/dist/observability/bounded-sink.d.ts.map +1 -1
- package/dist/primitives/canonical-json.d.ts +23 -2
- package/dist/primitives/canonical-json.d.ts.map +1 -1
- package/dist/primitives/delivery.d.ts.map +1 -1
- package/dist/primitives/index.d.ts +1 -1
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/money.d.ts.map +1 -1
- package/dist/process/cleanup.d.ts +6 -1
- package/dist/process/cleanup.d.ts.map +1 -1
- package/dist/process/command-lifetime.d.ts +4 -0
- package/dist/process/command-lifetime.d.ts.map +1 -1
- package/dist/process/command-owner.d.ts.map +1 -1
- package/dist/process/command.d.ts +4 -0
- package/dist/process/command.d.ts.map +1 -1
- package/dist/process/contract.d.ts +67 -2
- package/dist/process/contract.d.ts.map +1 -1
- package/dist/process/group.d.ts +40 -3
- package/dist/process/group.d.ts.map +1 -1
- package/dist/process/launch.d.ts +12 -1
- package/dist/process/launch.d.ts.map +1 -1
- package/dist/process/terminate.d.ts +39 -0
- package/dist/process/terminate.d.ts.map +1 -0
- package/dist/react/query-client.d.ts.map +1 -1
- package/dist/realtime/rejection.d.ts.map +1 -1
- package/dist/server/contract-stream.d.ts.map +1 -1
- package/dist/server/handler-state.d.ts.map +1 -1
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/request-body.d.ts +0 -1
- package/dist/server/request-body.d.ts.map +1 -1
- package/dist/server/unix-client.d.ts.map +1 -1
- package/dist/telegram/broadcast-delivery.d.ts.map +1 -1
- package/dist/telegram/broadcast-failure.d.ts +10 -2
- package/dist/telegram/broadcast-failure.d.ts.map +1 -1
- package/dist/telegram/operator-channel.d.ts.map +1 -1
- package/dist/telegram/screens/outcome.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +1 -1
- package/dist/testing/surface-conformance.d.ts.map +1 -1
- package/dist/testing/surface-manifest.d.ts +0 -6
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/cli/args.d.ts +1 -1
- package/dist/tools/cli/args.d.ts.map +1 -1
- package/dist/tools/cli/help.d.ts.map +1 -1
- package/dist/tools/cli/invoke.d.ts +2 -2
- package/dist/tools/cli/invoke.d.ts.map +1 -1
- package/dist/tools/cli/profile.d.ts.map +1 -1
- package/dist/tools/cli/publication-assets.d.ts.map +1 -1
- package/dist/tools/cli/publication-contract.d.ts +8 -0
- package/dist/tools/cli/publication-contract.d.ts.map +1 -1
- package/dist/tools/cli/publication-control.d.ts.map +1 -1
- package/dist/tools/cli/publication-storage.d.ts +23 -2
- package/dist/tools/cli/publication-storage.d.ts.map +1 -1
- package/dist/tools/cli/publication.d.ts +8 -1
- package/dist/tools/cli/publication.d.ts.map +1 -1
- package/dist/tools/connections/errors.d.ts +18 -6
- package/dist/tools/connections/errors.d.ts.map +1 -1
- package/dist/tools/connections/limits.d.ts +8 -2
- package/dist/tools/connections/limits.d.ts.map +1 -1
- package/dist/tools/connections/mcp-client.d.ts.map +1 -1
- package/dist/tools/connections/mcp-envelope.d.ts.map +1 -1
- package/dist/tools/connections/openapi.d.ts.map +1 -1
- package/dist/tools/connections/schema-budget.d.ts +1 -1
- package/dist/tools/connections/schema-budget.d.ts.map +1 -1
- package/dist/tools/connections/sse-session.d.ts.map +1 -1
- package/dist/tools/execute-args.d.ts.map +1 -1
- package/dist/tools/execute-result.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +17 -3
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/internal/tool-error-projection.d.ts +16 -0
- package/dist/tools/internal/tool-error-projection.d.ts.map +1 -0
- package/dist/tools/internal/tool-view.d.ts.map +1 -1
- package/dist/tools/invoker.d.ts.map +1 -1
- package/dist/tools/list-names.d.ts +3 -2
- package/dist/tools/list-names.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts +3 -2
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/mcp/handler.d.ts +7 -0
- package/dist/tools/mcp/handler.d.ts.map +1 -1
- package/dist/tools/mcp/mount.d.ts +18 -2
- package/dist/tools/mcp/mount.d.ts.map +1 -1
- package/dist/tools/mcp/prepare.d.ts +3 -3
- package/dist/tools/mcp/prepare.d.ts.map +1 -1
- package/dist/tools/mcp/register.d.ts.map +1 -1
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
- package/dist/tools/mcp/stdio-signals.d.ts +14 -0
- package/dist/tools/mcp/stdio-signals.d.ts.map +1 -1
- package/dist/tools/mcp/stdio.d.ts +7 -0
- package/dist/tools/mcp/stdio.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-runtime-tools.d.ts.map +1 -1
- package/dist/tools/operations/async-operation-runtime-types.d.ts +3 -3
- package/dist/tools/operations/async-operation-runtime-types.d.ts.map +1 -1
- package/dist/tools/operations/define-wait-tool.d.ts +1 -1
- package/dist/tools/operations/define-wait-tool.d.ts.map +1 -1
- package/dist/tools/operations/wait-core.d.ts +0 -6
- package/dist/tools/operations/wait-core.d.ts.map +1 -1
- package/dist/tools/registry.d.ts +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/remote.d.ts +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/{runtime-tool-execution.d.ts → runtime-tool-declaration.d.ts} +32 -9
- package/dist/tools/runtime-tool-declaration.d.ts.map +1 -0
- package/dist/tools/runtime-tool-mcp.d.ts +6 -15
- package/dist/tools/runtime-tool-mcp.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +14 -12
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/schema/coerce.d.ts +4 -0
- package/dist/tools/schema/coerce.d.ts.map +1 -1
- package/dist/tools/schema/flatten-join.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +7 -11
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/transfer/define-download-tool.d.ts +1 -1
- package/dist/tools/transfer/define-download-tool.d.ts.map +1 -1
- package/dist/tools/transfer/define-upload-tool.d.ts +1 -1
- package/dist/tools/transfer/define-upload-tool.d.ts.map +1 -1
- package/dist/tools/transfer/define-view-file-tool.d.ts +1 -1
- package/dist/tools/transfer/define-view-file-tool.d.ts.map +1 -1
- package/dist/tools/transfer/managed-file-error.d.ts.map +1 -1
- package/dist/tools/transfer/view-file-contract.d.ts.map +1 -1
- package/dist/tools/transfer/view-file.d.ts +1 -1
- package/dist/tools/transfer/view-file.d.ts.map +1 -1
- package/dist/tools/transports.d.ts +3 -2
- package/dist/tools/transports.d.ts.map +1 -1
- package/llms/agent-runtime-browser.txt +10 -4
- package/llms/agent-runtime-harness.txt +32 -31
- package/llms/agent-runtime-react.txt +1 -1
- package/llms/agent-runtime-realtime.txt +14 -5
- package/llms/agent-runtime-sandbox.txt +3 -11
- package/llms/agent-runtime.3.txt +2 -2
- package/llms/agent-runtime.txt +0 -9
- package/llms/application.2.txt +3 -12
- package/llms/application.3.txt +7 -4
- package/llms/application.txt +44 -15
- package/llms/cli-publish.txt +29 -0
- package/llms/cli.2.txt +9 -11
- package/llms/cli.txt +26 -14
- package/llms/declaration.txt +0 -9
- package/llms/files-packaging.txt +22 -7
- package/llms/files.txt +4 -2
- package/llms/geo.txt +0 -9
- package/llms/live.txt +4 -13
- package/llms/observability.txt +31 -5
- package/llms/primitives.txt +38 -20
- package/llms/process.txt +305 -154
- package/llms/react-keyboard.txt +0 -9
- package/llms/react.txt +0 -18
- package/llms/server.4.txt +1 -1
- package/llms/server.5.txt +1 -1
- package/llms/server.txt +1 -1
- package/llms/stitchkit.txt +6 -7
- package/llms/telegram-screens.txt +2 -2
- package/llms/testing.txt +0 -1
- package/llms/tools-mcp.txt +2 -3
- package/llms/tools.2.txt +12 -0
- package/llms/tools.3.txt +5 -6
- package/llms/tools.txt +6 -4
- package/llms/upgrading-0.37.0-0.50.0.txt +947 -0
- package/llms/{upgrading-0.44.0-0.61.0.txt → upgrading-0.53.0-0.67.0.txt} +576 -506
- package/llms/{upgrading-0.62.0-0.80.0.txt → upgrading-0.68.0-0.94.0.txt} +562 -576
- package/llms/{upgrading-0.81.0-0.104.0.txt → upgrading-0.95.0-0.105.0.txt} +452 -567
- package/llms/upgrading.txt +4 -4
- package/llms-full.txt +1053 -405
- package/llms.txt +24 -24
- package/native-assets.json +11 -3
- package/package.json +6 -6
- package/dist/agent-runtime/harness-tools.d.ts +0 -11
- package/dist/agent-runtime/harness-tools.d.ts.map +0 -1
- package/dist/entrypoints/agent-runtime/harness-tools.d.ts +0 -2
- package/dist/entrypoints/agent-runtime/harness-tools.d.ts.map +0 -1
- package/dist/entrypoints/agent-runtime/harness-tools.js +0 -32
- package/dist/index-1a6jtnd9.js +0 -88
- package/dist/index-8a9ksm91.js +0 -95
- package/dist/index-a3wma1hk.js +0 -48
- package/dist/index-jd470k6q.js +0 -109
- package/dist/index-n1dqr7gf.js +0 -20
- package/dist/index-nggfa2yn.js +0 -34
- package/dist/index-s4evrmp5.js +0 -98
- package/dist/index-ze1eph1k.js +0 -20
- package/dist/tools/connections/error-projection.d.ts +0 -4
- package/dist/tools/connections/error-projection.d.ts.map +0 -1
- package/dist/tools/runtime-tool-execution.d.ts.map +0 -1
- package/llms/agent-runtime-harness-tools.txt +0 -25
- package/llms/upgrading-0.37.0-0.44.0.txt +0 -444
package/llms-full.txt
CHANGED
|
@@ -59,6 +59,7 @@ own, recorded as an ADR.
|
|
|
59
59
|
| `stitchkit/tools/invoker` | server | stable | peer-free `createToolInvoker` over the canonical contract tool runner |
|
|
60
60
|
| `stitchkit/tools/connections` | server (Bun or Node) | evolving | consume external MCP servers and a bounded OpenAPI subset — typed reauthorization, a per-request host fence and per-call credentials |
|
|
61
61
|
| `stitchkit/cli` | server | stable | `createCli` / `createCliInvoker` — runtime and strict NodeNext declarations without MCP SDK / `ai` |
|
|
62
|
+
| `stitchkit/cli/publish` | build and release tooling (Bun or Node, POSIX) | evolving | `publishCli` — commit a verified CLI distribution and move its public manifest last; see [CLI guide](cli.md#publishing-a-complete-version) |
|
|
62
63
|
| `stitchkit/remote` | browser **and** server | stable | peer-free `implementRemote` for thin HTTP proxy processes |
|
|
63
64
|
| `stitchkit/process` | server (Bun or Node, POSIX) | evolving | finite native commands, byte sinks, bounded capture and process-group cancellation |
|
|
64
65
|
| `stitchkit/files/packaging` | build time (Bun or Node) | evolving | installed native asset graph, integrity and Bun packaging plugin; no runtime install policy |
|
|
@@ -73,25 +74,24 @@ own, recorded as an ADR.
|
|
|
73
74
|
| `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
|
|
74
75
|
| `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
|
|
75
76
|
| `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
|
|
76
|
-
| `stitchkit/observability` | server | stable<br>_redefined in 2 of the
|
|
77
|
+
| `stitchkit/observability` | server | stable<br>_redefined in 2 of the 50 minors since 0.56.2, most recently 0.92.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
77
78
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the managed-resource conformance kit |
|
|
78
79
|
| `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
79
80
|
| `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
|
|
80
81
|
| `stitchkit/react/keyboard` | browser | evolving | which part of a screen gets a key — ordered layers, one Escape per level, list zones with real focus; see [keyboard guide](keyboard.md) |
|
|
81
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in
|
|
82
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 23 of the 50 minors since 0.56.2, most recently 0.105.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
82
83
|
| `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
|
|
83
84
|
| `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults, deterministic race controls and the agent-store conformance kit |
|
|
84
85
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
85
86
|
| `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 |
|
|
86
87
|
| `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
|
|
87
88
|
| `stitchkit/agent-runtime/browser` | browser + server | evolving | canonical agent records, events, reconnect cursor and optional conversation controller |
|
|
88
|
-
| `stitchkit/agent-runtime/
|
|
89
|
-
| `stitchkit/agent-runtime/
|
|
90
|
-
| `stitchkit/agent-runtime/realtime` | server (Bun or Node) | evolving | authorized agent control over the existing Socket.IO server |
|
|
89
|
+
| `stitchkit/agent-runtime/react` | browser + server | evolving, experimental: no committed consumer | React subscription to an application-owned agent controller |
|
|
90
|
+
| `stitchkit/agent-runtime/realtime` | server (Bun or Node) | evolving, experimental: no committed consumer | authorized agent control over the existing Socket.IO server |
|
|
91
91
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
92
92
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
93
93
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
94
|
-
| `stitchkit/application` | browser + server | evolving<br>_redefined in
|
|
94
|
+
| `stitchkit/application` | browser + server | evolving<br>_redefined in 13 of the 50 minors since 0.56.2, most recently 0.105.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
|
|
95
95
|
| `stitchkit/application/grammy` | server | evolving | a grammY bot as application resources — configuration, then polling with updates admitted by batch — and the webhook adapter |
|
|
96
96
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
97
97
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
@@ -197,7 +197,6 @@ map — feature → packages:
|
|
|
197
197
|
| MCP / agent adapters (`stitchkit/tools`) | `@modelcontextprotocol/server` `ai` |
|
|
198
198
|
| Agent application runtime (`stitchkit/agent-runtime`) | `ai` |
|
|
199
199
|
| Headless Agent harness (`stitchkit/agent-runtime/harness`) | `ai` |
|
|
200
|
-
| Harness tools (`stitchkit/agent-runtime/harness-tools`) | `ai` `@modelcontextprotocol/server` (tool declarations) |
|
|
201
200
|
| Agent coding tools (`stitchkit/agent-runtime/coding-tools`) | — |
|
|
202
201
|
| OpenRouter runtime adapter (`stitchkit/agent-runtime/openrouter`) | `ai` `@openrouter/ai-sdk-provider` |
|
|
203
202
|
| SQLite agent store (`stitchkit/agent-runtime/sqlite/bun` or `/node`) | — (runtime built-in) |
|
|
@@ -1207,7 +1206,7 @@ Three boundaries worth knowing:
|
|
|
1207
1206
|
|
|
1208
1207
|
- A field of another scope is not a compile error on access — `RuntimeContext`
|
|
1209
1208
|
keeps its `[key: string]: unknown` index signature (transports write through
|
|
1210
|
-
it). It degrades to `unknown`, so it
|
|
1209
|
+
it). It degrades to `unknown`, so it cannot pose as a `string`; using it
|
|
1211
1210
|
in a typed position fails.
|
|
1212
1211
|
- The map states what **your** `beforeHandle` / `createAuthHook.inject` puts on
|
|
1213
1212
|
the context. The framework does not verify a hand-written claim (→ ADR 0075) —
|
|
@@ -4229,15 +4228,17 @@ For example, an optional object `value` in `single` and a required array `value`
|
|
|
4229
4228
|
in `group` advertise:
|
|
4230
4229
|
|
|
4231
4230
|
```text
|
|
4232
|
-
When kind = group: Two to ten references.
|
|
4233
4231
|
When kind = single: One reference.
|
|
4234
|
-
|
|
4232
|
+
When kind = group: Two to ten references.
|
|
4233
|
+
Available if kind = single | group. Required if kind = group
|
|
4235
4234
|
```
|
|
4236
4235
|
|
|
4237
4236
|
A third branch without `value` is absent from those labels. The object and array
|
|
4238
4237
|
retain their separate shapes; the array's minimum length does not apply to the object.
|
|
4239
|
-
Discriminator values, branch explanation labels and joined
|
|
4240
|
-
|
|
4238
|
+
Discriminator values, branch explanation labels, enum values and joined
|
|
4239
|
+
properties keep the order of their first appearance across the variants, so the
|
|
4240
|
+
order you declare is the order the model reads. Alternatives of different kinds
|
|
4241
|
+
keep one canonical order, so one declaration always yields one document. → ADR [0221](../decisions/0221-flat-field-explanations-retain-branch-meaning.md).
|
|
4241
4242
|
|
|
4242
4243
|
**A field several variants declare keeps its type.** Flattening puts every
|
|
4243
4244
|
variant's fields side by side, so a key two variants share has to be advertised
|
|
@@ -4487,6 +4488,18 @@ An explicit `AppError.retryable` boolean also survives this remote hop. `false`
|
|
|
4487
4488
|
`true` on409 remain the application's recommendation through HTTP, CLI, MCP and AI tools.
|
|
4488
4489
|
Absent metadata keeps the previous defaults and does not alter HTTP transport retries.
|
|
4489
4490
|
|
|
4491
|
+
A remote failure that carries no stitchkit error envelope — an HTML `503` from a proxy
|
|
4492
|
+
during a deploy, a `429` from a rate limiter, a `404` page, a refused connection — keeps
|
|
4493
|
+
what the caller can act on and drops what it must not see. A response with a status
|
|
4494
|
+
becomes `HTTP_ERROR` with that status and a fixed message; a failure with no response
|
|
4495
|
+
becomes `CONNECTION_REQUEST_FAILED` (`502`). Neither carries `details`: the upstream
|
|
4496
|
+
body, URL and credentials stay in the raw cause that `onToolError` and `AgentToolError.cause`
|
|
4497
|
+
expose in-process. One rule decides `retryable` for every origin: the declared value wins,
|
|
4498
|
+
otherwise the status decides (`408`, `429`, `502`, `503`, `504` are retryable, as for any
|
|
4499
|
+
local tool). A call the caller cancelled (`REQUEST_ABORTED`) declares `retryable: false`;
|
|
4500
|
+
a deadline the remote client hit (`REQUEST_TIMEOUT`, `408`) follows the status like a
|
|
4501
|
+
`408` from a local handler.
|
|
4502
|
+
|
|
4490
4503
|
## Structured output
|
|
4491
4504
|
|
|
4492
4505
|
When a contract endpoint or runtime tool declares `output`, its MCP tool
|
|
@@ -5147,15 +5160,6 @@ identical. Write the handler once; it serves every surface.
|
|
|
5147
5160
|
# Guide: Agent application runtime (docs/guide/agent-runtime.md)
|
|
5148
5161
|
==============================================================================
|
|
5149
5162
|
|
|
5150
|
-
---
|
|
5151
|
-
title: "Agent application runtime"
|
|
5152
|
-
description: Configure Stitchkit's optional durable history, stream loop, run coordination and managed-tool fencing.
|
|
5153
|
-
type: architecture
|
|
5154
|
-
status: active
|
|
5155
|
-
created: 2026-08-22
|
|
5156
|
-
updated: 2026-10-02 16:28 +07:00
|
|
5157
|
-
---
|
|
5158
|
-
|
|
5159
5163
|
# Agent application runtime
|
|
5160
5164
|
|
|
5161
5165
|
> **Maturity: evolving.** This surface is still finding its shape and may be
|
|
@@ -6697,20 +6701,6 @@ existing runtime configurations need no migration.
|
|
|
6697
6701
|
# Guide: Agent composition (docs/guide/agent-composition.md)
|
|
6698
6702
|
==============================================================================
|
|
6699
6703
|
|
|
6700
|
-
---
|
|
6701
|
-
title: "Agent composition without a second runtime"
|
|
6702
|
-
description: "Compose contract tools, authenticated realtime control, React views and exact-run behavioral checks."
|
|
6703
|
-
type: architecture
|
|
6704
|
-
status: active
|
|
6705
|
-
created: 2026-09-30 22:45 +07:00
|
|
6706
|
-
updated: 2026-09-30 23:03 +07:00
|
|
6707
|
-
participants:
|
|
6708
|
-
- role: authored
|
|
6709
|
-
harness: Codex
|
|
6710
|
-
model: GPT-6
|
|
6711
|
-
at: 2026-09-30 22:45 +07:00
|
|
6712
|
-
---
|
|
6713
|
-
|
|
6714
6704
|
# Compose an agent application
|
|
6715
6705
|
|
|
6716
6706
|
Use these pieces when an application wants the existing headless harness and a generic
|
|
@@ -6726,7 +6716,7 @@ import {
|
|
|
6726
6716
|
createAgentHarnessFileResources,
|
|
6727
6717
|
createHeadlessAgentHarness,
|
|
6728
6718
|
} from 'stitchkit/agent-runtime/harness';
|
|
6729
|
-
import {
|
|
6719
|
+
import { composeToolLifecycle, mountAgent } from 'stitchkit/tools';
|
|
6730
6720
|
|
|
6731
6721
|
const resources = createAgentHarnessFileResources({
|
|
6732
6722
|
roots: [{ id: 'guide', kind: 'instruction', path: instructionRoot }],
|
|
@@ -6735,27 +6725,31 @@ const resources = createAgentHarnessFileResources({
|
|
|
6735
6725
|
const harness = createHeadlessAgentHarness({
|
|
6736
6726
|
protocol, store, models, promptBudget,
|
|
6737
6727
|
resources,
|
|
6738
|
-
tools:
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6728
|
+
tools: ({ context, run, toolFenceLifecycle }) =>
|
|
6729
|
+
mountAgent([catalogService], {
|
|
6730
|
+
context: { ...context, messageId: run.assistantMessageId },
|
|
6731
|
+
runtimeTools: resources.runtimeTools,
|
|
6732
|
+
lifecycle: composeToolLifecycle(authorizeTools, toolFenceLifecycle),
|
|
6733
|
+
hooks: observeTools,
|
|
6734
|
+
}),
|
|
6745
6735
|
});
|
|
6746
6736
|
```
|
|
6747
6737
|
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
coercion, output stripping, hooks and durability
|
|
6753
|
-
|
|
6738
|
+
`tools` is called for each run with its validated context. Mount through `mountAgent` and
|
|
6739
|
+
compose your authorization with the run's `toolFenceLifecycle` by `composeToolLifecycle`: the
|
|
6740
|
+
fence rejects a call from a run that no longer owns the conversation, and it must run exactly
|
|
6741
|
+
once. Every mount option remains available, including registry, runtime tools, extension,
|
|
6742
|
+
coercion, output stripping, hooks and durability, and the callback may be async. The same callback
|
|
6743
|
+
still accepts arbitrary typed AI SDK tools and prepareStep. `stitchkit/tools` needs the same `ai` and
|
|
6744
|
+
`@modelcontextprotocol/server` peers as any `mountAgent` use; the base harness stays AI-only.
|
|
6754
6745
|
The resource reader is explicitly selected: filtered allowlists do not gain tools merely
|
|
6755
6746
|
because a loader exists. Root IDs, kinds, provenance and limits are unchanged.
|
|
6756
6747
|
|
|
6757
6748
|
## Server and browser
|
|
6758
6749
|
|
|
6750
|
+
`stitchkit/agent-runtime/realtime` and `stitchkit/agent-runtime/react` are evolving and
|
|
6751
|
+
experimental: no application has committed to them yet, so expect their shape to change.
|
|
6752
|
+
|
|
6759
6753
|
```ts
|
|
6760
6754
|
import { bindAgentHarnessRealtime } from 'stitchkit/agent-runtime/realtime';
|
|
6761
6755
|
|
|
@@ -6767,11 +6761,19 @@ const binding = bindAgentHarnessRealtime(harness, socket, {
|
|
|
6767
6761
|
},
|
|
6768
6762
|
onError: reportServerError,
|
|
6769
6763
|
});
|
|
6764
|
+
|
|
6765
|
+
// When an application withdraws someone's access, tell the binding.
|
|
6766
|
+
await removeMember(conversationId, userId);
|
|
6767
|
+
binding.revoke(conversationId, (identity) => identity.userId === userId);
|
|
6770
6768
|
```
|
|
6771
6769
|
|
|
6772
6770
|
`socket` is the existing `createSocketIOServer` handle, attached to `createServer` (Bun)
|
|
6773
|
-
or the Node adapter. The access policy checks every
|
|
6774
|
-
|
|
6771
|
+
or the Node adapter. The access policy checks every request, file/reference and metadata, and
|
|
6772
|
+
the attach that opens a stream. Events then flow under that grant without a call per token;
|
|
6773
|
+
Without a `revoke()` call a grant lasts until the socket disconnects or the conversation is detached: events keep flowing even if your own permission data changed. That fail-open default is deliberate (one check per request, not one per token), so call `revoke` whenever access is withdrawn. `revoke` ends the grant, the next event is re-authorized (`source: 'delivery'`), and a refusal
|
|
6774
|
+
or failure detaches the conversation and delivers `access-denied`, which puts the controller in
|
|
6775
|
+
`error` with code `FORBIDDEN` instead of leaving a frozen view. Session handshake
|
|
6776
|
+
authentication alone is insufficient.
|
|
6775
6777
|
Conversation ownership and observer/controller roles are application policy; the canonical
|
|
6776
6778
|
control server additionally enforces its exclusive controller lease.
|
|
6777
6779
|
|
|
@@ -6809,7 +6811,10 @@ view does not interrupt its remote run. Use explicit `interrupt` for cancellatio
|
|
|
6809
6811
|
Reconnect reattaches and reads durable state. Mutations are not retried automatically:
|
|
6810
6812
|
if an acknowledgement is lost, retain the original idempotency key. Incoming event
|
|
6811
6813
|
buffers and outgoing requests have finite limits; capacity/overflow/lease failures stay
|
|
6812
|
-
visible
|
|
6814
|
+
visible, each with a code from the one `AgentControlErrorCode` vocabulary. A reconnect
|
|
6815
|
+
rejects the requests of the previous connection at once and attaches again, so they
|
|
6816
|
+
cannot hold capacity the new attach needs. A server overflow deliberately disconnects the
|
|
6817
|
+
transport; reconnect explicitly.
|
|
6813
6818
|
|
|
6814
6819
|
## Check behavior, not just admission
|
|
6815
6820
|
|
|
@@ -6837,15 +6842,6 @@ The helper does not invoke a model or poll; the existing runtime ticket owns exe
|
|
|
6837
6842
|
# Guide: Managed application kernel (docs/guide/application-kernel.md)
|
|
6838
6843
|
==============================================================================
|
|
6839
6844
|
|
|
6840
|
-
---
|
|
6841
|
-
title: Managed application kernel
|
|
6842
|
-
description: Compose process-local resources, schedules, readiness, drain and optional provider adapters without building a second job platform.
|
|
6843
|
-
type: architecture
|
|
6844
|
-
status: active
|
|
6845
|
-
created: 2026-08-23
|
|
6846
|
-
updated: 2026-10-04 13:28 +07:00
|
|
6847
|
-
---
|
|
6848
|
-
|
|
6849
6845
|
# Managed application kernel
|
|
6850
6846
|
|
|
6851
6847
|
> **Maturity: evolving.** This surface is still finding its shape and may be
|
|
@@ -7029,9 +7025,8 @@ lifecycle context; the application decides when a database/provider probe runs.
|
|
|
7029
7025
|
|
|
7030
7026
|
**A `reportHealth` call inside `start` is kept.** A resource that says nothing
|
|
7031
7027
|
is assumed healthy once it is ready; one that reports its own health has already
|
|
7032
|
-
answered the question, and the answer stands.
|
|
7033
|
-
|
|
7034
|
-
overwrote it.)
|
|
7028
|
+
answered the question, and the answer stands. The readiness default never
|
|
7029
|
+
overwrites it.
|
|
7035
7030
|
|
|
7036
7031
|
**Reporting the same health again is not a change.** A resource may confirm its
|
|
7037
7032
|
health on a timer; a report with the value it already reported publishes
|
|
@@ -7527,9 +7522,48 @@ The caller owns file selection and coordination with an active rotator.
|
|
|
7527
7522
|
Writer startup inspects retained and active files under its lock. On damage,
|
|
7528
7523
|
`journal.getStatus().recovery` contains counts and first/last anomaly locations, reconstructed
|
|
7529
7524
|
again on restart from the preserved bytes. This describes startup observations, not live
|
|
7530
|
-
continuous inspection.
|
|
7531
|
-
|
|
7532
|
-
|
|
7525
|
+
continuous inspection. Normal rotation retains the existing finite eviction policy.
|
|
7526
|
+
→ [ADR 0219](../decisions/0219-journal-damage-is-data-and-io-failure-is-failure.md).
|
|
7527
|
+
|
|
7528
|
+
A file opening cannot keep in place — a torn active file with `maxFiles: 1`, a generation name
|
|
7529
|
+
that is not a regular file, a retained file it cannot read — is renamed to
|
|
7530
|
+
`<file>.quarantined-<epoch>` beside it, and the journal starts (`onStartupRefusal: 'quarantine'`,
|
|
7531
|
+
the default). Nothing is deleted; quarantined files are outside retention and yours to remove.
|
|
7532
|
+
Every open lists them in `getStatus().recovery.quarantined` until you do: the ones it moved
|
|
7533
|
+
first, with their `reason`, then the ones earlier runs left, without one — at most 32, and
|
|
7534
|
+
`quarantinedUnlisted` counts the rest.
|
|
7535
|
+
A journal whose damage must stop the process — one that is a source of truth, not diagnostics —
|
|
7536
|
+
declares `onStartupRefusal: 'fail'` and gets a typed `DiagnosticJournalRecoveryError`:
|
|
7537
|
+
|
|
7538
|
+
```ts
|
|
7539
|
+
import { DiagnosticJournalRecoveryError } from 'stitchkit/application'
|
|
7540
|
+
|
|
7541
|
+
// A diagnostic journal: start, then say what is set aside.
|
|
7542
|
+
const journal = await createDiagnosticJournal({ ...config })
|
|
7543
|
+
const { quarantined = [], quarantinedUnlisted = 0 } = journal.getStatus().recovery ?? {}
|
|
7544
|
+
for (const entry of quarantined) {
|
|
7545
|
+
// `reason` is present only for a file this start moved; an earlier run's has none.
|
|
7546
|
+
internalLogger.warn({ file: entry.file, movedTo: entry.quarantinedAs, reason: entry.reason })
|
|
7547
|
+
}
|
|
7548
|
+
if (quarantinedUnlisted > 0) internalLogger.warn({ quarantinedUnlisted })
|
|
7549
|
+
|
|
7550
|
+
// A journal that is a source of truth: refuse to start over damage.
|
|
7551
|
+
try {
|
|
7552
|
+
await createDiagnosticJournal({ ...config, onStartupRefusal: 'fail' })
|
|
7553
|
+
} catch (error) {
|
|
7554
|
+
if (error instanceof DiagnosticJournalRecoveryError) {
|
|
7555
|
+
// error.reason: 'torn-without-retention-slot' | 'not-a-regular-file' | 'unreadable'
|
|
7556
|
+
}
|
|
7557
|
+
throw error
|
|
7558
|
+
}
|
|
7559
|
+
```
|
|
7560
|
+
|
|
7561
|
+
A quarantining journal writes, so the managed resource that owns it reports `healthy`, and its
|
|
7562
|
+
dependants read `recovery` from the value they `use()`. There is no degraded journal: the kernel
|
|
7563
|
+
degrades only optional resources, and a required resource cannot depend on one.
|
|
7564
|
+
`DiagnosticJournalRecoveryStatusSchema` is evolving and `.strict()`; if you embed it in your own
|
|
7565
|
+
protocol, every change to its shape is listed under breaking changes.
|
|
7566
|
+
→ [ADR 0240](../decisions/0240-a-diagnostic-journal-quarantines-what-it-cannot-keep.md).
|
|
7533
7567
|
|
|
7534
7568
|
Shutdown performs one phase barrier at a time: stop admission everywhere,
|
|
7535
7569
|
cancel future schedules, drain admitted work, then close in reverse stable
|
|
@@ -7983,17 +8017,6 @@ boundary is required. Send and projection callbacks own their external deadlines
|
|
|
7983
8017
|
# Guide: Optional process sandbox (docs/guide/sandbox.md)
|
|
7984
8018
|
==============================================================================
|
|
7985
8019
|
|
|
7986
|
-
---
|
|
7987
|
-
title: Optional process sandbox
|
|
7988
|
-
description: Linux namespace isolation, durable workspaces and a host credential gateway.
|
|
7989
|
-
updated: 2026-10-03 14:47 +07:00
|
|
7990
|
-
participants:
|
|
7991
|
-
- role: implemented
|
|
7992
|
-
harness: Codex
|
|
7993
|
-
model: GPT-6
|
|
7994
|
-
at: 2026-10-03 14:47 +07:00
|
|
7995
|
-
---
|
|
7996
|
-
|
|
7997
8020
|
# Optional process sandbox
|
|
7998
8021
|
|
|
7999
8022
|
`stitchkit/agent-runtime/sandbox` is a server-only, evolving API. Existing coding
|
|
@@ -8057,6 +8080,9 @@ access the same workspace through the host's contained-file implementation;
|
|
|
8057
8080
|
commands run inside the namespace with `/workspace` as their root. The host-only
|
|
8058
8081
|
`handle.coding` binding never belongs in model input or persisted state.
|
|
8059
8082
|
Unsupported backends omit the binding and the factory refuses explicitly.
|
|
8083
|
+
When the adapter's `probe()` or `prepare()` fails, the agent gets `SANDBOX_UNAVAILABLE` with a
|
|
8084
|
+
fixed reason; the adapter's own error is the refusal's `cause`, delivered to the mount's
|
|
8085
|
+
`hooks.onToolError`, which is where an application logs it.
|
|
8060
8086
|
|
|
8061
8087
|
Stop also kills coding commands. Both launch paths share the configured
|
|
8062
8088
|
`maxConcurrentCommands` admission limit (default 8); an occupied slot gives
|
|
@@ -8125,37 +8151,25 @@ be refused explicitly by the backend.
|
|
|
8125
8151
|
# Guide: Native IO (docs/guide/native-io.md)
|
|
8126
8152
|
==============================================================================
|
|
8127
8153
|
|
|
8128
|
-
---
|
|
8129
|
-
title: Конечные команды и файловые гарантии
|
|
8130
|
-
description: Native IO для Bun и Node, с явно выбранной durability, безопасными bounds и caller lifetime.
|
|
8131
|
-
type: guide
|
|
8132
|
-
status: active
|
|
8133
|
-
created: 2026-10-01 20:44 +07:00
|
|
8134
|
-
updated: 2026-10-04 20:04 +07:00
|
|
8135
|
-
participants:
|
|
8136
|
-
- role: authored
|
|
8137
|
-
harness: Codex
|
|
8138
|
-
model: GPT-6
|
|
8139
|
-
at: 2026-10-01 20:44 +07:00
|
|
8140
|
-
- role: implemented
|
|
8141
|
-
harness: Codex
|
|
8142
|
-
model: GPT-6
|
|
8143
|
-
at: 2026-10-02 00:08 +07:00
|
|
8144
|
-
- role: implemented
|
|
8145
|
-
harness: Codex
|
|
8146
|
-
model: GPT-6
|
|
8147
|
-
at: 2026-10-03 13:36 +07:00
|
|
8148
|
-
- role: implemented
|
|
8149
|
-
harness: Codex
|
|
8150
|
-
model: GPT-6
|
|
8151
|
-
at: 2026-10-04 09:21 +07:00
|
|
8152
|
-
---
|
|
8153
|
-
|
|
8154
8154
|
# Native IO
|
|
8155
8155
|
|
|
8156
|
-
|
|
8156
|
+
Shortest working example: publish a file atomically, then run one bounded command.
|
|
8157
|
+
|
|
8158
|
+
```ts
|
|
8159
|
+
import { writeFileAtomic } from 'stitchkit/files'
|
|
8160
|
+
import { runNativeCommand } from 'stitchkit/process'
|
|
8161
|
+
|
|
8162
|
+
await writeFileAtomic('/srv/app/state.json', '{"version":2}')
|
|
8163
|
+
const { exitCode, stdout } = await runNativeCommand({
|
|
8164
|
+
executable: '/usr/bin/git', args: ['rev-parse', 'HEAD'], cwd: '/srv/app',
|
|
8165
|
+
capture: true, maxOutputBytes: 4096, timeoutMs: 5000,
|
|
8166
|
+
})
|
|
8167
|
+
console.log(exitCode, new TextDecoder().decode(stdout))
|
|
8168
|
+
```
|
|
8157
8169
|
|
|
8158
|
-
|
|
8170
|
+
The examples use public imports and run the same in Bun and Node >= 22.
|
|
8171
|
+
|
|
8172
|
+
## Writing a file
|
|
8159
8173
|
|
|
8160
8174
|
```ts
|
|
8161
8175
|
import { writeFileAtomic, AtomicFilePublicationError } from 'stitchkit/files'
|
|
@@ -8168,7 +8182,7 @@ try {
|
|
|
8168
8182
|
})
|
|
8169
8183
|
} catch (error) {
|
|
8170
8184
|
if (error instanceof AtomicFilePublicationError) {
|
|
8171
|
-
// target
|
|
8185
|
+
// The target is already visible. Check the receipt before deciding whether to retry.
|
|
8172
8186
|
console.error(error.phase, error.cause)
|
|
8173
8187
|
} else throw error
|
|
8174
8188
|
}
|
|
@@ -8177,25 +8191,50 @@ await writeFileAtomic('/srv/app/state.json', '{"version":2}', {
|
|
|
8177
8191
|
})
|
|
8178
8192
|
```
|
|
8179
8193
|
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
Create
|
|
8185
|
-
|
|
8194
|
+
The defaults are `replace: true`, `durability: 'file'` and `mode: 0o600`: file fsync and atomic
|
|
8195
|
+
replacement. `durability: 'none'` skips every sync. `'directory'` runs the full sequence: write all
|
|
8196
|
+
bytes, exact descriptor chmod, file fsync, rename (replace) or link (create), remove the staging
|
|
8197
|
+
link, parent directory fsync. `writeFileAtomicSync` takes the same options and runs the same order.
|
|
8198
|
+
Create fails with the native `EEXIST` when the target exists, is a symlink, or a concurrent writer
|
|
8199
|
+
won. Never turn that refusal into a replace automatically.
|
|
8200
|
+
|
|
8201
|
+
`AtomicFilePublicationError.published === true` tells a visible target from an unconfirmed
|
|
8202
|
+
acknowledgement. `phase` is `cleanup`, `directory-sync` or `directory-close`; the primary cause is
|
|
8203
|
+
kept in `cause`. An error before publication leaves the previous target untouched. Cleanup errors are
|
|
8204
|
+
secondary to the primary error. While a create is in flight a second hardlink briefly exists, so a
|
|
8205
|
+
strict single-link reader may refuse in that window.
|
|
8206
|
+
|
|
8207
|
+
### Abandoned staging files
|
|
8208
|
+
|
|
8209
|
+
The bytes are staged beside the target under `.stitchkit-<24 lowercase hex>.tmp`, the same name for
|
|
8210
|
+
`writeFileAtomic`, `writeFileAtomicSync`, the managed writer and the chunk spool. A process killed
|
|
8211
|
+
between the staging and the publication (SIGKILL, OOM, power loss) leaves that file behind, and no
|
|
8212
|
+
later write removes it: a random name alone cannot tell an abandoned write from one in flight.
|
|
8213
|
+
Removing it is the caller's responsibility:
|
|
8214
|
+
|
|
8215
|
+
```ts
|
|
8216
|
+
import { isAtomicStagingName, sweepAtomicStaging } from 'stitchkit/files'
|
|
8217
|
+
|
|
8218
|
+
// At startup, or before trusting that a directory holds only your own records:
|
|
8219
|
+
const removed = await sweepAtomicStaging({ directory: '/srv/app/registry', olderThanMs: 60 * 60_000 })
|
|
8186
8220
|
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
При create кратко существует второй hardlink; strict single-link reader вправе отказать в этом окне.
|
|
8221
|
+
// Skip staging files when listing a directory of your records:
|
|
8222
|
+
const records = (await readdir('/srv/app/registry')).filter((name) => !isAtomicStagingName(name))
|
|
8223
|
+
```
|
|
8191
8224
|
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
`
|
|
8196
|
-
|
|
8225
|
+
`olderThanMs` is required and must exceed the longest write in flight; a younger staging file is
|
|
8226
|
+
left alone. The sweep removes only regular files, never follows a symlink and never enters a
|
|
8227
|
+
subdirectory. The name form is a stable public contract: changing it is a breaking change of
|
|
8228
|
+
`stitchkit/files`. An exclusive lock's `.lock-*.tmp` file is not an atomic-write staging file and
|
|
8229
|
+
does not match.
|
|
8197
8230
|
|
|
8198
|
-
|
|
8231
|
+
Directory fsync is a native POSIX filesystem capability. Windows refuses it explicitly, and another
|
|
8232
|
+
filesystem may refuse at open or sync. A check on Linux does not confirm macOS behavior, network
|
|
8233
|
+
filesystems or survival of a power loss; the disk state is whatever the filesystem and OS guarantee.
|
|
8234
|
+
`createManagedFileBoundary.write(..., { durable: true })` uses the same publish owner and parent sync;
|
|
8235
|
+
its create stays a create, and `replace: true` allows replacement explicitly.
|
|
8236
|
+
|
|
8237
|
+
## Strict reads
|
|
8199
8238
|
|
|
8200
8239
|
```ts
|
|
8201
8240
|
import { createManagedFileBoundary } from 'stitchkit/files'
|
|
@@ -8206,25 +8245,27 @@ const source = await files.read('receipt.json', {
|
|
|
8206
8245
|
})
|
|
8207
8246
|
console.log(source.observation) // dev, ino, size, nlink, mtimeMs, ctimeMs
|
|
8208
8247
|
const receipt = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(source.bytes))
|
|
8209
|
-
//
|
|
8248
|
+
// Schema parsing and digest checks belong to the application.
|
|
8210
8249
|
```
|
|
8211
8250
|
|
|
8212
|
-
|
|
8213
|
-
`rejectSymlinks`
|
|
8214
|
-
`stable`
|
|
8215
|
-
`
|
|
8216
|
-
`FILE_UNSUPPORTED`
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8251
|
+
Without options the result is `{ ref, bytes }` and a trusted in-root symlink or hardlink is
|
|
8252
|
+
allowed. `rejectSymlinks` refuses a symlink at the requested leaf, `singleLink` checks `nlink` of
|
|
8253
|
+
the opened descriptor, `stable` compares dev/ino/size/mtime/ctime/nlink around the byte read, and
|
|
8254
|
+
`observe` adds the metadata. These refusals are distinct: `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`,
|
|
8255
|
+
`FILE_UNSAFE_LINK`, `FILE_TOO_LARGE`, `FILE_CHANGED`, `FILE_UNSUPPORTED` and `FILE_IO_ERROR`. Abort
|
|
8256
|
+
keeps the caller's reason. A nonblocking open lets a FIFO be refused without waiting for a writer.
|
|
8257
|
+
Bytes are bounded while reading, including a file that grows, not only by the initial stat. Chunk
|
|
8258
|
+
allocation is at most `maxBytes + 1 + 64 KiB` and the flattened result at most `maxBytes`. The
|
|
8259
|
+
descriptor closes on success, refusal, abort and IO error. A close error keeps the primary read error;
|
|
8260
|
+
after a successful read a close error is returned to the caller.
|
|
8221
8261
|
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
snapshot
|
|
8262
|
+
The root and its ancestors belong to a trusted actor. These options do not give
|
|
8263
|
+
descriptor-relative containment against a hostile concurrent ancestor replacement, and there is no
|
|
8264
|
+
switch to treat ancestors as safe: the portable owner has no such capability. Metadata stability does
|
|
8265
|
+
not prove an immutable snapshot against an arbitrary hostile writer; use an application lock or
|
|
8266
|
+
immutable publication.
|
|
8226
8267
|
|
|
8227
|
-
##
|
|
8268
|
+
## Commands
|
|
8228
8269
|
|
|
8229
8270
|
```ts
|
|
8230
8271
|
import { runNativeCommand } from 'stitchkit/process'
|
|
@@ -8246,21 +8287,23 @@ await runNativeCommand({
|
|
|
8246
8287
|
})
|
|
8247
8288
|
```
|
|
8248
8289
|
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
`
|
|
8253
|
-
|
|
8254
|
-
`
|
|
8255
|
-
|
|
8256
|
-
|
|
8290
|
+
A command needs a caller `signal` or a finite `timeoutMs` (1 to 2147483647 ms, the native timer
|
|
8291
|
+
range; a larger value is refused, never turned into 1 ms). Streaming runs for the caller's lifetime,
|
|
8292
|
+
with no hidden 60-second timeout or 1 MiB output limit. `capture` defaults to false; `true` requires
|
|
8293
|
+
`maxOutputBytes` for the combined stdout and stderr. The limit counts source bytes; the API returns
|
|
8294
|
+
`Uint8Array` and does not decode invalid UTF-8. The result holds the observed `exitCode: number | null`
|
|
8295
|
+
and `signal: string | null`; a signal termination is never turned into an invented exit code.
|
|
8296
|
+
`maxOutputBytes` may bound streaming too. `stdin` is bytes bounded by `maxStdinBytes` (default 1 MiB).
|
|
8297
|
+
`envPolicy` defaults to `declared-only`: no `env` means an empty environment, and `ambient` inherits
|
|
8298
|
+
explicitly. The application owns cwd and executable policy; the primitive launches a native
|
|
8299
|
+
executable and never creates a shell.
|
|
8257
8300
|
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8301
|
+
`NativeCommandError.reason` classifies a limit error by machine: `deadline` when the command's own
|
|
8302
|
+
`timeoutMs` expires, `output-budget` when the combined stdout and stderr bytes exceed the limit, in
|
|
8303
|
+
capture or streaming. Both keep `code: 'COMMAND_LIMIT'`; `message` is for diagnosis, not for
|
|
8304
|
+
protocols. `COMMAND_UNAVAILABLE` and `COMMAND_CLEANUP` have no reason. A caller abort and a sink
|
|
8305
|
+
failure keep the original error. When cleanup fails, the original limit and its reason stay inside the
|
|
8306
|
+
`AggregateError` cause.
|
|
8264
8307
|
|
|
8265
8308
|
```ts
|
|
8266
8309
|
import { NativeCommandError } from 'stitchkit/process'
|
|
@@ -8276,38 +8319,112 @@ try {
|
|
|
8276
8319
|
}
|
|
8277
8320
|
```
|
|
8278
8321
|
|
|
8279
|
-
|
|
8280
|
-
`{ cause, reason: 'deadline' | 'output-budget' }`
|
|
8281
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
bounded native Readable buffer
|
|
8286
|
-
lifetime signal. Abort
|
|
8287
|
-
|
|
8288
|
-
IO,
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
Cleanup
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
AggregateError cause.
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8322
|
+
The constructor is `new NativeCommandError(code, message, { cause })`; it accepts
|
|
8323
|
+
`{ cause, reason: 'deadline' | 'output-budget' }` only for `COMMAND_LIMIT`. The owner always sets the
|
|
8324
|
+
reason for its own limits. An error a caller constructs without a reason gets none inferred from the
|
|
8325
|
+
text; its `reason` is `undefined`.
|
|
8326
|
+
|
|
8327
|
+
Output is read with backpressure, stdout and stderr separately: one running sink per channel plus a
|
|
8328
|
+
bounded native Readable buffer, with no accumulation of the whole history while streaming. The
|
|
8329
|
+
callback receives the lifetime signal. Abort releases the wait even for a callback that ignores the
|
|
8330
|
+
signal, but it cannot cancel third-party work the callback continues after the abort. A sink must
|
|
8331
|
+
cancel its own IO, must not keep an unbounded history and must not change shared descriptor flags.
|
|
8332
|
+
After an abort the owner starts no new callbacks. Normal completion waits for all bytes and sinks.
|
|
8333
|
+
|
|
8334
|
+
Cleanup uses the POSIX process group under the `stop` policy, then KILL; `cleanupTimeoutMs`
|
|
8335
|
+
(default 2000, maximum 30 000) bounds the wait for close after the signals. On
|
|
8336
|
+
Darwin a transient `EPERM` when signalling a group can mean zombies the OS reaper has not collected
|
|
8337
|
+
yet. The retry is bounded by `cleanupTimeoutMs`; success needs a delivered signal or `ESRCH`, and a
|
|
8338
|
+
permanent refusal is kept in the cause. The close of every owned pipe and the leader's exit are
|
|
8339
|
+
observed separately, including the absence of a shared `child.close` during teardown. The caller or
|
|
8340
|
+
sink cause is kept; a cleanup failure is a `NativeCommandError` with `COMMAND_CLEANUP` and an
|
|
8341
|
+
`AggregateError` cause. Even an exited parent does not exclude a helper that holds a pipe. Windows
|
|
8342
|
+
refuses with `COMMAND_UNAVAILABLE`; a descendant that leaves the group on its own with `setsid` is
|
|
8343
|
+
outside this capability. This is one-shot execution, not a PTY, a supervisor or a daemon host.
|
|
8344
|
+
|
|
8345
|
+
### Stopping a command: the `stop` policy
|
|
8346
|
+
|
|
8347
|
+
A caller abort, the `timeoutMs` deadline, an exceeded output budget and a failing sink all stop the
|
|
8348
|
+
command the same way, by its `stop` policy:
|
|
8349
|
+
|
|
8350
|
+
| field | meaning |
|
|
8351
|
+
|---|---|
|
|
8352
|
+
| `target` | `'group'`: `signal` goes to every member of the group. `'leader'`: `signal` goes to the leader alone; the leader's exit ends the grace at once. Required. |
|
|
8353
|
+
| `signal` | `SIGTERM` (default), `SIGINT`, `SIGHUP`, `SIGQUIT`, `SIGUSR1` or `SIGUSR2`. KILL is not a stop signal: it is what the grace ends in. |
|
|
8354
|
+
| `graceMs` | How long the leader (or the group) has to leave before the whole group gets KILL. 0 to 3 600 000 (one hour). Required. |
|
|
8355
|
+
| `killOn` | Optional `AbortSignal`. Its abort stops the command with KILL to the whole group at once, also in the middle of a grace. |
|
|
8356
|
+
|
|
8357
|
+
Omitting `stop` means `{ target: 'group', signal: 'SIGTERM', graceMs: 100 }`.
|
|
8358
|
+
|
|
8359
|
+
```ts
|
|
8360
|
+
// Cooperative cancellation: the leader rolls its work back, its helpers are not asked.
|
|
8361
|
+
await runNativeCommand({
|
|
8362
|
+
executable: '/usr/local/bin/migrate', args: ['apply'], signal: request.signal,
|
|
8363
|
+
stop: { target: 'leader', signal: 'SIGINT', graceMs: 15 * 60_000, killOn: shutdown.signal },
|
|
8364
|
+
})
|
|
8365
|
+
```
|
|
8366
|
+
|
|
8367
|
+
With `target: 'leader'` the rest of the group never receives the cooperative signal: they are
|
|
8368
|
+
processes the leader started, and the leader decides what happens to them. Whatever is left in the
|
|
8369
|
+
group when the leader exits, or when the grace ends, gets KILL, including a helper with its stdio
|
|
8370
|
+
closed that no pipe would have revealed. A leader that has already exited is never signalled again.
|
|
8371
|
+
|
|
8372
|
+
The output pipes stay open until the stop sequence ends. Output a stopping command writes is read and
|
|
8373
|
+
dropped, never passed to `onOutput` and never captured, so a leader that writes while it shuts down
|
|
8374
|
+
does not meet a broken pipe. The result rejects with the abort reason once the group is gone and the
|
|
8375
|
+
pipes are closed, so it can take `graceMs` plus `cleanupTimeoutMs` after the abort.
|
|
8376
|
+
|
|
8377
|
+
The grace is a timer inside the process that runs the command. If that process exits first, the
|
|
8378
|
+
timer is gone and the group, which is detached into its own session, keeps running: nothing in this
|
|
8379
|
+
package can bound it from outside. Abort `killOn` in your shutdown path, so the group is killed
|
|
8380
|
+
before the process exits, and let the supervisor own the case where the process dies without a
|
|
8381
|
+
shutdown. Under systemd, keep `KillMode=control-group` (the default) so the stop of the unit
|
|
8382
|
+
reaches every process in its cgroup, whatever its process group; launchd only kills the job's own
|
|
8383
|
+
process group, which a command's group is not.
|
|
8384
|
+
|
|
8385
|
+
### Stopping a group and its descendants
|
|
8386
|
+
|
|
8387
|
+
A group is signalled only while the kernel still reports members. `ESRCH` from the first signal,
|
|
8388
|
+
or from the existence probe that follows it during the grace, means no member is left: no KILL
|
|
8389
|
+
is sent afterwards, because a numeric group id that has vanished can already belong to an
|
|
8390
|
+
unrelated group. A group that is still visible after the grace period receives KILL.
|
|
8391
|
+
|
|
8392
|
+
`descendants` declares what happens to processes the leader left running once it has exited
|
|
8393
|
+
successfully:
|
|
8394
|
+
|
|
8395
|
+
| `descendants` | After the leader exits |
|
|
8396
|
+
|---|---|
|
|
8397
|
+
| `'terminate-after-leader'` (the default) | The group is stopped right after `onLeaderSettled` returns and before the pipes drain: with `target: 'group'` by the same signal, grace, KILL sequence as cancellation; with `target: 'leader'` by KILL at once, since the leader has already exited. Descendants that left the group with `setsid` stay out of reach. |
|
|
8398
|
+
| `'leave'` | Nothing. A helper started with its stdio detached keeps running. One that inherited stdout or stderr keeps the pipes open, and the command then waits for it until its deadline or abort. |
|
|
8399
|
+
|
|
8400
|
+
Omitting the option means `'terminate-after-leader'`: a command does not leak the helpers it
|
|
8401
|
+
started. A real daemon detaches into its own session (`setsid`, as `daemon(3)` does), is no member
|
|
8402
|
+
of the group and is unaffected. Declare `'leave'` only for a helper that has to outlive the
|
|
8403
|
+
command while staying in its group:
|
|
8404
|
+
|
|
8405
|
+
```ts
|
|
8406
|
+
await runNativeCommand({
|
|
8407
|
+
executable: '/usr/local/bin/start-helper', timeoutMs: 60_000,
|
|
8408
|
+
// the helper started here outlives this command on purpose
|
|
8409
|
+
descendants: 'leave',
|
|
8410
|
+
})
|
|
8411
|
+
```
|
|
8412
|
+
|
|
8413
|
+
A command whose group this package did not create (a structural
|
|
8414
|
+
launcher supplied by a host) has no group to stop, and `descendants` has nothing to act on there.
|
|
8415
|
+
|
|
8416
|
+
|
|
8417
|
+
## Replacing hand-written wrappers
|
|
8418
|
+
|
|
8419
|
+
An atomic-file wrapper becomes one `writeFileAtomic` call with the chosen options. A bounded
|
|
8420
|
+
regular-file read becomes `files.read`; JSON and schema parsing, receipt conflicts, journal rotation
|
|
8421
|
+
and application locks stay with the application. A spawn, drain, capture and cancellation wrapper
|
|
8422
|
+
becomes `runNativeCommand`; shell policy, command admission, logging, redaction, the bytes-to-text
|
|
8423
|
+
decoder and the operator sink stay with the application. Long signal-only commands stay streaming;
|
|
8424
|
+
bounded tar or capture commands get their own explicit limits. Importing one leaf does not make
|
|
8425
|
+
installing the whole npm package smaller; choose a dependency after measuring packed size and its
|
|
8426
|
+
dependency closure.
|
|
8427
|
+
|
|
8311
8428
|
## Resource-scoped launchers and shared owners
|
|
8312
8429
|
|
|
8313
8430
|
`onLeaderSettled(event, signal)` observes leader exit before inherited stdout/stderr pipes
|
|
@@ -8349,8 +8466,23 @@ syscall interruptible or provide a snapshot against a hostile writer.
|
|
|
8349
8466
|
An exclusive lock applies exact requested permissions through its creating descriptor,
|
|
8350
8467
|
independent of umask. Default `0600` stays private. Shared `0640` requires a common group and
|
|
8351
8468
|
traversable directories; another UID also needs directory write permission to reclaim a
|
|
8352
|
-
proven-dead owner's file.
|
|
8353
|
-
|
|
8469
|
+
proven-dead owner's file.
|
|
8470
|
+
|
|
8471
|
+
The lock is published with its owner already recorded: the owner record is written to a
|
|
8472
|
+
private temporary file in the lock's directory, then hard-linked to the lock name. `link`
|
|
8473
|
+
fails with `EEXIST` for every caller but one, and the name never exists without a complete
|
|
8474
|
+
record, so a holder that stalls or dies before publishing leaves no lock and cannot be
|
|
8475
|
+
displaced. The directory must therefore be on a filesystem that supports hard links. A crash
|
|
8476
|
+
between the temporary write and the link can leave a `.lock-*.tmp` file behind; it is not a
|
|
8477
|
+
lock and nothing waits on it.
|
|
8478
|
+
|
|
8479
|
+
`ownerlessGraceMs` defaults to `null`: a lock file with no readable owner is never taken
|
|
8480
|
+
because of its age. This library does not produce such a file; only a writer that creates the name
|
|
8481
|
+
before its owner record can leave one (for example an empty file from a crashed process).
|
|
8482
|
+
`ownerlessGraceMs: <ms>` opts in to reclaiming an empty lock file older than that, and also governs
|
|
8483
|
+
abandoned empty reclaim guards.
|
|
8484
|
+
When unset, an empty reclaim guard left by an older writer is taken after 5 000 ms so recovery
|
|
8485
|
+
is never disabled for good; `null` refuses that too.
|
|
8354
8486
|
Stale reclaim guards use the same lock owner and a child guard for the inode check and
|
|
8355
8487
|
unlink. Recovery depth is capped at 16; a deeper stale chain refuses recovery with a depth diagnosis retained as the cause of
|
|
8356
8488
|
the bounded `LOCK_TIMEOUT`. Live owners, unknown process-lifetime evidence and unsafe records are never
|
|
@@ -8376,27 +8508,26 @@ version: carry their reachable relative `.d.ts` closure, rather than emitting re
|
|
|
8376
8508
|
instead emit declarations for only that DTO contract. Keep the library's manifest at runtime
|
|
8377
8509
|
`dependencies: {}` and `peerDependencies: { zod: ... }` when that is its declared promise.
|
|
8378
8510
|
|
|
8379
|
-
The packaged Darwin loader
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8383
|
-
|
|
8511
|
+
The packaged Darwin loader names its addons by a path computed at run time, so a bundler
|
|
8512
|
+
never follows it: bundling `stitchkit/server`, `stitchkit/files` or `stitchkit/process` yields
|
|
8513
|
+
the same single JS file on every operating system, with no `.node` output, and
|
|
8514
|
+
`bun build --outfile` works. Such a bundle looks for the addon beside the original package
|
|
8515
|
+
files, never inside itself. Where that file is absent (an artifact moved to another machine)
|
|
8516
|
+
the Darwin backend is `unavailable`: process identity cannot be recorded, so a lock held by a
|
|
8517
|
+
crashed owner is not reclaimed. An artifact that must carry the Darwin addon uses the packaging
|
|
8518
|
+
plugin of the next section; it is the only way a static loader enters a bundle.
|
|
8384
8519
|
|
|
8385
8520
|
```sh
|
|
8386
|
-
bun build src/native.ts --target=bun --minify --
|
|
8387
|
-
bun build src/native.ts --compile --bytecode --format=esm --outfile=dist/native
|
|
8521
|
+
bun build src/native.ts --target=bun --minify --outfile=dist/native.js
|
|
8388
8522
|
```
|
|
8389
8523
|
|
|
8390
|
-
For a JS build that previously used `--outfile=dist/native.js`, use
|
|
8391
|
-
`--outdir=dist --entry-naming=native.js` instead: the output can now include native assets.
|
|
8392
|
-
The standalone executable still uses `--outfile` because those assets are embedded inside it.
|
|
8393
|
-
|
|
8394
8524
|
Qualify the complete output outside its build tree and installed dependency graph. Test process
|
|
8395
|
-
identity, live-owner refusal, dead-owner recovery and contained file operations in
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8525
|
+
identity, live-owner refusal, dead-owner recovery and contained file operations in artifacts built
|
|
8526
|
+
with the packaging plugin, and check that an unpackaged build reports the backend `unavailable`
|
|
8527
|
+
rather than certifying a dead owner. Bun and Node package imports retain the same
|
|
8528
|
+
lazy loader; importing a portable leaf does not load a Darwin addon on another OS. Test
|
|
8529
|
+
declarations too, and check that importing an unbundled Stitchkit leaf fails in a deliberately
|
|
8530
|
+
isolated library distribution. A separate schema-only entry must not import the native entry.
|
|
8400
8531
|
|
|
8401
8532
|
If the Darwin backend cannot load, `observeProcessInstance` still returns `unavailable`.
|
|
8402
8533
|
Its backend `Error` preserves the original `cause`; JSON serialization carries only the stable
|
|
@@ -8412,17 +8543,17 @@ Stitchkit installation or create an independent lightweight npm package.
|
|
|
8412
8543
|
## Public native packaging
|
|
8413
8544
|
|
|
8414
8545
|
Custom archives and installers use `stitchkit/files/packaging` at **build time**.
|
|
8415
|
-
`createNativePackaging` resolves the installed version and target addon
|
|
8416
|
-
|
|
8546
|
+
`createNativePackaging` resolves the installed version and target addon, reads the addon once and
|
|
8547
|
+
checks it against the size and SHA256 that Stitchkit published in its `native-assets.json` when the
|
|
8548
|
+
package was built. A ready asset carries those verified `bytes` with the published `size` and
|
|
8549
|
+
`sha256`; its Bun-compatible plugin integrates the same bytes with the same lazy native loader.
|
|
8550
|
+
Each call reads and hashes the selected addons, so call it once per build and reuse the result.
|
|
8417
8551
|
The leaf is evolving. It requires no optional peer or Bun ambient declarations for Node imports.
|
|
8418
8552
|
It must run unbundled from its installed package, never from an application's runtime artifact.
|
|
8419
|
-
This contract is available from 0.104.1; versions 0.103.13–0.104.0 use the complete-output recipe
|
|
8420
|
-
in the previous section.
|
|
8421
8553
|
|
|
8422
8554
|
```ts
|
|
8423
8555
|
import { createNativePackaging } from 'stitchkit/files/packaging'
|
|
8424
|
-
import {
|
|
8425
|
-
import { copyFileSync, mkdirSync, readFileSync } from 'node:fs'
|
|
8556
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
8426
8557
|
import { dirname, join } from 'node:path'
|
|
8427
8558
|
|
|
8428
8559
|
const native = createNativePackaging({
|
|
@@ -8436,14 +8567,11 @@ const result = await Bun.build({
|
|
|
8436
8567
|
plugins: [native.plugin],
|
|
8437
8568
|
})
|
|
8438
8569
|
if (!result.success) throw new AggregateError(result.logs, 'Build failed')
|
|
8439
|
-
// Preserve every result.outputs file under dist, then
|
|
8570
|
+
// Preserve every result.outputs file under dist, then write the verified companion bytes.
|
|
8440
8571
|
for (const asset of native.assets) {
|
|
8441
|
-
const bytes = readFileSync(asset.sourcePath)
|
|
8442
|
-
if (createHash('sha256').update(bytes).digest('hex') !== asset.sha256)
|
|
8443
|
-
throw new Error('Native asset changed during build')
|
|
8444
8572
|
const destination = join('dist', asset.outputPath)
|
|
8445
8573
|
mkdirSync(dirname(destination), { recursive: true })
|
|
8446
|
-
|
|
8574
|
+
writeFileSync(destination, asset.bytes)
|
|
8447
8575
|
}
|
|
8448
8576
|
```
|
|
8449
8577
|
|
|
@@ -8452,23 +8580,67 @@ absolute paths, traversal or overlapping file/directory paths. `entryPath` is fi
|
|
|
8452
8580
|
Bun naming templates such as `[dir]` or `[name]`. Companion JS builds require one entry, no splitting,
|
|
8453
8581
|
and `naming.entry` exactly matching `entryPath`; the plugin rejects a mismatching layout.
|
|
8454
8582
|
Choose any application layout; the framework does not prescribe an app directory or installer.
|
|
8455
|
-
Keep every bundler output
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8583
|
+
Keep every bundler output and write `asset.bytes`; never read the addon from `node_modules` a
|
|
8584
|
+
second time. An installed addon whose size or SHA256 differs from the published manifest refuses
|
|
8585
|
+
with `mismatch` / `NATIVE_ASSET_DIGEST_MISMATCH`, so a file substituted after installation never
|
|
8586
|
+
reaches an artifact. Archive the complete output directory with an integrity manifest that records
|
|
8587
|
+
`asset.sha256`, unpack to a clean directory, verify the same hashes there, and run offline without
|
|
8588
|
+
the build tree or `node_modules`. The published digest ties the bytes to the package Stitchkit
|
|
8589
|
+
built; the package itself is only as trusted as its tarball (the lockfile integrity of the install).
|
|
8590
|
+
Signature and trust policy remain application-owned.
|
|
8591
|
+
|
|
8592
|
+
One JS artifact can run on both Darwin architectures; change only the packaging inputs:
|
|
8593
|
+
|
|
8594
|
+
```ts
|
|
8595
|
+
const native = createNativePackaging({
|
|
8596
|
+
platform: 'darwin', architecture: ['arm64', 'x64'], delivery: 'companion',
|
|
8597
|
+
entryPath: 'app/native.js',
|
|
8598
|
+
assetPath: { arm64: 'addons/arm.node', x64: 'addons/intel.node' },
|
|
8599
|
+
})
|
|
8600
|
+
```
|
|
8601
|
+
|
|
8602
|
+
Use the same single plugin and write loop above. A single-target loader deliberately refuses the
|
|
8603
|
+
other architecture; two competing plugins cannot produce a universal loader. The array form
|
|
8604
|
+
requires exactly one distinct output path per declared target. A ready result carries the target
|
|
8605
|
+
array and both verified assets with their published digests; `NativePackagingOptions<true>` /
|
|
8606
|
+
`NativePackagingResult<true>` describe this form when explicitly annotating variables.
|
|
8607
|
+
The loader selects only `process.arch`: a missing or wrong-architecture selected addon refuses,
|
|
8608
|
+
even when the other valid addon is present. Linux runtime imports remain lazy and use Linux
|
|
8609
|
+
process primitives without loading Darwin assets. Qualify the exact same JS bytes on real
|
|
8610
|
+
Darwin arm64 and x64 machines, with both companions preserved through archive delivery.
|
|
8611
|
+
Build-machine architecture and cross-build success cannot establish universal native support.
|
|
8459
8612
|
|
|
8460
8613
|
For a standalone executable use `delivery: 'embedded'` and the same plugin in `Bun.build`
|
|
8461
|
-
with `compile: { outfile: ... }`. Bun embeds the selected addon
|
|
8462
|
-
|
|
8614
|
+
with `compile: { outfile: ... }`. Bun embeds the selected addon from the verified bytes the
|
|
8615
|
+
plugin holds, not from a second read of the file; `assets` identifies those bytes and their
|
|
8616
|
+
published digest for qualification, not a companion that must be installed beside the executable.
|
|
8463
8617
|
Cross-builds must select the requested architecture explicitly; the artifact must run on that target.
|
|
8464
8618
|
The installed package must contain that target's addon. No automatic fallback/downgrade occurs.
|
|
8465
8619
|
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8620
|
+
`platform` is the closed set of platforms with native addons, today `'darwin'`: another name is a
|
|
8621
|
+
type error and a schema refusal that throws, so pass the literal (or narrow `process.platform`
|
|
8622
|
+
with `=== 'darwin'`) rather than a free string. The API returns `unsupported` /
|
|
8623
|
+
`NATIVE_TARGET_UNSUPPORTED` for an unsupported architecture, `missing` / `NATIVE_ASSET_MISSING` for
|
|
8624
|
+
an addon the package does not publish or whose file is gone, and `mismatch` /
|
|
8625
|
+
`NATIVE_ASSET_DIGEST_MISMATCH` for an addon whose bytes differ from the published size or SHA256.
|
|
8626
|
+
A `mismatch` names the first differing `architecture` and carries `expected` (the published
|
|
8627
|
+
`{ size, sha256 }`) and `actual` (the installed file's size and the SHA256 of the bytes read), so
|
|
8628
|
+
a build log can tell a truncated copy from a substituted one:
|
|
8629
|
+
|
|
8630
|
+
```ts
|
|
8631
|
+
if (native.state === 'mismatch')
|
|
8632
|
+
throw new Error(
|
|
8633
|
+
`${native.architecture} addon: expected ${native.expected.size} bytes ` +
|
|
8634
|
+
`${native.expected.sha256}, found ${native.actual.size} bytes ${native.actual.sha256}`,
|
|
8635
|
+
)
|
|
8636
|
+
```
|
|
8637
|
+
|
|
8638
|
+
Portable libraries
|
|
8639
|
+
and Linux builds use their normal build, without a Darwin plugin; merely importing the build leaf
|
|
8640
|
+
loads no addon.
|
|
8641
|
+
Malformed inputs, a manifest whose `formatVersion` is not 2 and other IO errors throw before packaging.
|
|
8642
|
+
A missing or corrupt companion at runtime yields the native `unavailable` result with the safe
|
|
8643
|
+
stage and code from the observer; it never certifies a dead owner. Preserve the raw cause internally.
|
|
8472
8644
|
The plugin does not parse consumer JS or establish a second runtime native implementation.
|
|
8473
8645
|
|
|
8474
8646
|
## Qualification boundaries
|
|
@@ -8487,24 +8659,19 @@ the qualification for native Darwin behavior.
|
|
|
8487
8659
|
|
|
8488
8660
|
Local filesystem tests do not attest delayed NFS visibility, shared-volume microVM or gVisor
|
|
8489
8661
|
ownership, actual PGID reuse, power-loss durability or macOS full-sync behavior. Those
|
|
8490
|
-
require their actual topology or failure environment.
|
|
8491
|
-
|
|
8492
|
-
|
|
8662
|
+
require their actual topology or failure environment. No age-only observation proves a
|
|
8663
|
+
process dead; the lock's owner record exists before its name does.
|
|
8664
|
+
|
|
8665
|
+
A sandbox admission slot is released once a direct command's pipes have closed and its owner has settled.
|
|
8666
|
+
A `COMMAND_CLEANUP` outcome means the death of its group was not proven, so the slot stays
|
|
8667
|
+
occupied, `stop` keeps rejecting with the cleanup error, and the sandbox session is recreated to
|
|
8668
|
+
recover.
|
|
8493
8669
|
|
|
8494
8670
|
|
|
8495
8671
|
==============================================================================
|
|
8496
8672
|
# Guide: Generic application primitives (docs/guide/primitives.md)
|
|
8497
8673
|
==============================================================================
|
|
8498
8674
|
|
|
8499
|
-
---
|
|
8500
|
-
title: Generic application primitives
|
|
8501
|
-
description: Declare lifecycle, access, exact values, audit, delivery and exports without moving application infrastructure into Stitchkit.
|
|
8502
|
-
type: guide
|
|
8503
|
-
status: active
|
|
8504
|
-
created: 2026-09-01
|
|
8505
|
-
updated: 2026-10-01 21:03 +07:00
|
|
8506
|
-
---
|
|
8507
|
-
|
|
8508
8675
|
# Generic application primitives
|
|
8509
8676
|
|
|
8510
8677
|
`stitchkit/primitives` is an optional browser-safe leaf. It declares values and policies; it does
|
|
@@ -8607,33 +8774,50 @@ const bytes = canonicalJson({ '2': 'two', '10': 'ten', optional: undefined, nest
|
|
|
8607
8774
|
// Native crypto or browser WebCrypto hashes these same UTF-8 bytes.
|
|
8608
8775
|
```
|
|
8609
8776
|
|
|
8610
|
-
`canonicalJson`
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
negative zero, cycles, user prototypes, accessors
|
|
8614
|
-
|
|
8777
|
+
`canonicalJson` is the one serializer behind the digests: object keys sort by UTF-16 code unit,
|
|
8778
|
+
integer-like keys included; array order is kept; object members that are `undefined` are omitted.
|
|
8779
|
+
It refuses, before serializing, a top-level or array `undefined`, sparse arrays, functions, symbols,
|
|
8780
|
+
BigInt, non-finite numbers, negative zero, cycles, user prototypes, accessors and non-enumerable or
|
|
8781
|
+
symbol members. Validate your own schema first; the canonical boundary does not replace it.
|
|
8782
|
+
|
|
8783
|
+
Defaults: depth 100 (the maximum), nodes 100 000, UTF-8 bytes 1 MiB. Invalid options throw a Zod
|
|
8784
|
+
validation error. A refused value throws `CanonicalJsonError`, a `TypeError` whose `reason` is a
|
|
8785
|
+
`CanonicalJsonRefusal`; branch on the reason, never on the message:
|
|
8615
8786
|
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8787
|
+
| `reason` | Meaning |
|
|
8788
|
+
|---|---|
|
|
8789
|
+
| `depth` | The value is nested deeper than `maxDepth`. |
|
|
8790
|
+
| `nodes` | The value has more nodes than `maxNodes`. |
|
|
8791
|
+
| `bytes` | The output is longer than `maxBytes` in UTF-8. |
|
|
8792
|
+
| `cycle` | The value contains a cycle. |
|
|
8793
|
+
| `negative-zero` | The value contains `-0`, which JSON cannot carry. |
|
|
8794
|
+
| `not-json` | The value is not bounded plain JSON: sparse arrays, non-finite numbers, non-plain objects, accessors. |
|
|
8795
|
+
|
|
8796
|
+
`depth`, `nodes` and `bytes` are the caller's limits being exceeded; the other three say the value is
|
|
8797
|
+
not JSON data at all.
|
|
8798
|
+
|
|
8799
|
+
```ts
|
|
8800
|
+
import { CanonicalJsonError, canonicalJson } from 'stitchkit/primitives'
|
|
8801
|
+
|
|
8802
|
+
try {
|
|
8803
|
+
canonicalJson(payload, { maxBytes: 4096 })
|
|
8804
|
+
} catch (error) {
|
|
8805
|
+
if (error instanceof CanonicalJsonError && error.reason === 'bytes') {
|
|
8806
|
+
// too large: reject the request
|
|
8807
|
+
} else throw error
|
|
8808
|
+
}
|
|
8809
|
+
```
|
|
8810
|
+
|
|
8811
|
+
The leaf is browser-safe: no Node, Bun or crypto, no `agent-runtime/testing`, no optional peers. Native
|
|
8812
|
+
and browser code run the same algorithm; hashing stays a platform adapter. A consumer that needs the
|
|
8813
|
+
same bytes moves only the serializer recursion and keeps its own proof schemas and verification
|
|
8814
|
+
boundary.
|
|
8622
8815
|
|
|
8623
8816
|
|
|
8624
8817
|
==============================================================================
|
|
8625
8818
|
# Guide: Application migration recipes (docs/guide/application-migration-recipes.md)
|
|
8626
8819
|
==============================================================================
|
|
8627
8820
|
|
|
8628
|
-
---
|
|
8629
|
-
title: Application migration recipes
|
|
8630
|
-
description: Executable patterns for moving database, poller, queue and operational publishing lifecycle into the managed application kernel.
|
|
8631
|
-
type: architecture
|
|
8632
|
-
status: active
|
|
8633
|
-
created: 2026-08-24
|
|
8634
|
-
updated: 2026-10-04 13:28 +07:00
|
|
8635
|
-
---
|
|
8636
|
-
|
|
8637
8821
|
# Application migration recipes
|
|
8638
8822
|
|
|
8639
8823
|
These recipes are the migration companion to the
|
|
@@ -8785,10 +8969,10 @@ guard must refuse. Filesystem mutations made inside inbox transactions call
|
|
|
8785
8969
|
`assertHeld` immediately before publication or cleanup.
|
|
8786
8970
|
|
|
8787
8971
|
For `createFileStateStore`, remove `staleLockMs` and `retryMs` from configuration.
|
|
8788
|
-
`lockTimeoutMs`
|
|
8972
|
+
`lockTimeoutMs` is the acquisition budget. Timestamp age never revokes
|
|
8789
8973
|
a live or unknown writer. Stop and verify old writers before recovering a legacy
|
|
8790
|
-
pid-only lock; do not invent owner metadata.
|
|
8791
|
-
|
|
8974
|
+
pid-only lock; do not invent owner metadata. Temporary files are never
|
|
8975
|
+
deleted by age. See the [durable intake contract](application-kernel.md#durable-process-facts-and-owner-notifications).
|
|
8792
8976
|
|
|
8793
8977
|
## Long-running poller
|
|
8794
8978
|
|
|
@@ -9036,9 +9220,10 @@ declarations do not require `ai`, the MCP SDK or Bun globals. Node consumers can
|
|
|
9036
9220
|
use strict `NodeNext` resolution with `skipLibCheck: false` and ordinary Node
|
|
9037
9221
|
types; the package keeps its required dependencies separate from optional SDKs.
|
|
9038
9222
|
|
|
9039
|
-
CLI runtime tools use the
|
|
9040
|
-
schemas, handler, and explicit CLI exposure. SDK presenters
|
|
9041
|
-
|
|
9223
|
+
CLI runtime tools use the one runtime-tool declaration: identity, input and output
|
|
9224
|
+
schemas, handler, and explicit CLI exposure. SDK presenters are the adapter's extension
|
|
9225
|
+
of that declaration (the fourth type argument of `RuntimeToolDefinitionWithOutput`, or
|
|
9226
|
+
`defineRuntimeTool` in `stitchkit/tools`), which requires the corresponding peers. A typed
|
|
9042
9227
|
definition returned by `defineRuntimeTool` or `createRuntimeToolFactory` can be
|
|
9043
9228
|
passed directly to both CLI and SDK mounts.
|
|
9044
9229
|
|
|
@@ -9052,7 +9237,7 @@ For a CLI-only managed tool, use the neutral generic construction contract to
|
|
|
9052
9237
|
infer the handler from its schemas:
|
|
9053
9238
|
|
|
9054
9239
|
```ts
|
|
9055
|
-
import type {
|
|
9240
|
+
import type { RuntimeToolDefinitionWithOutput } from 'stitchkit/cli'
|
|
9056
9241
|
import { z } from 'zod'
|
|
9057
9242
|
|
|
9058
9243
|
const input = z.object({ text: z.string() })
|
|
@@ -9062,12 +9247,14 @@ const measure = {
|
|
|
9062
9247
|
identity: { serviceName: 'text', action: 'measure', method: 'POST' },
|
|
9063
9248
|
transports: ['CLI'], input, output,
|
|
9064
9249
|
handler: ({ input }) => ({ size: input.text.length }),
|
|
9065
|
-
} satisfies
|
|
9250
|
+
} satisfies RuntimeToolDefinitionWithOutput<typeof input, typeof output>
|
|
9066
9251
|
```
|
|
9067
9252
|
|
|
9068
|
-
Pass `measure` to `runtimeTools`.
|
|
9069
|
-
|
|
9070
|
-
`
|
|
9253
|
+
Pass `measure` to `runtimeTools`. `RuntimeToolDefinition`, the heterogeneous
|
|
9254
|
+
registration type, accepts it unchanged, and types an inline handler written directly
|
|
9255
|
+
in `runtimeTools` by the parsed object (use the construction type above to type the
|
|
9256
|
+
input from the schema). Execute it through `createCli` or `createCliInvoker` so
|
|
9257
|
+
validation remains on the canonical path.
|
|
9071
9258
|
|
|
9072
9259
|
## Exposure is opt-in
|
|
9073
9260
|
|
|
@@ -9257,7 +9444,9 @@ the values agree. Duplicates are refused before command execution. Array
|
|
|
9257
9444
|
options accumulate across long and short forms; unknown non-dotted passthrough
|
|
9258
9445
|
options retain their raw-list grammar. Tokens after `--` are literal data.
|
|
9259
9446
|
Command `--help` and `-h` keep their diagnostic precedence over malformed
|
|
9260
|
-
operation arguments; application globals are validated before routing.
|
|
9447
|
+
operation arguments; application globals are validated before routing. Help is a
|
|
9448
|
+
request, not a setting, so repeating it in any spelling (`-h -h`, `-h --help`) asks once
|
|
9449
|
+
and never errors.
|
|
9261
9450
|
|
|
9262
9451
|
Without presentation configuration, positional arguments fill non-boolean
|
|
9263
9452
|
fields in declaration order, so `myapp generate "a fox"` is
|
|
@@ -9484,8 +9673,10 @@ Declared aliases render beside their canonical options, for example
|
|
|
9484
9673
|
The argument table and `Application options:` show bounds from their JSON Schema,
|
|
9485
9674
|
for example `<integer> [>=1, <=50]`, `<string> [length >=1, length <=12]`, and
|
|
9486
9675
|
`<value…> [items >=2, items <=4]`. Numeric `>` and `<` are exclusive, while `>=`
|
|
9487
|
-
and `<=` include the endpoint; a zero limit is displayed too.
|
|
9488
|
-
|
|
9676
|
+
and `<=` include the endpoint; a zero limit is displayed too. Only limits the
|
|
9677
|
+
schema declares are shown: the safe-integer range every `z.int()` carries is the
|
|
9678
|
+
type, not a bound, so a bare integer prints `<integer>`, `.min(1)` prints `>=1` and
|
|
9679
|
+
`.positive()` prints `>0`. Unbounded fields keep just their type and description. Bounds on an array's items are not bounds
|
|
9489
9680
|
on the array itself.
|
|
9490
9681
|
|
|
9491
9682
|
Composed schemas retain their meaning: `any of:` and `one of:` separate
|
|
@@ -9713,11 +9904,16 @@ exists to distrust.
|
|
|
9713
9904
|
|
|
9714
9905
|
`publishCli` is an opt-in publisher for a local application-owned distribution
|
|
9715
9906
|
directory. It commits every target and its version manifest before atomically
|
|
9716
|
-
replacing the public `manifest.json`.
|
|
9717
|
-
|
|
9907
|
+
replacing the public `manifest.json`. It ships from its own evolving entrypoint,
|
|
9908
|
+
`stitchkit/cli/publish`: release infrastructure follows how distributions are
|
|
9909
|
+
published, so it is not held to the stable promise of `stitchkit/cli`
|
|
9910
|
+
([ADR 0245](../decisions/0245-cli-publication-is-an-evolving-leaf.md)). The
|
|
9911
|
+
manifest, signatures, installer and updater it writes for stay in
|
|
9912
|
+
`stitchkit/cli`. Your application selects the CLI source identity and admits
|
|
9913
|
+
that snapshot at each phase:
|
|
9718
9914
|
|
|
9719
9915
|
```ts
|
|
9720
|
-
import { publishCli } from 'stitchkit/cli'
|
|
9916
|
+
import { publishCli } from 'stitchkit/cli/publish'
|
|
9721
9917
|
|
|
9722
9918
|
const result = await publishCli({
|
|
9723
9919
|
storageRoot: '/srv/myapp/cli',
|
|
@@ -10042,7 +10238,7 @@ await createCli({ name: 'myapp', version, runtimeTools: discovered, commands: [.
|
|
|
10042
10238
|
commands, and a connection without it still contributes nothing to the CLI —
|
|
10043
10239
|
exposure stays explicit, as it is everywhere else in the framework.
|
|
10044
10240
|
|
|
10045
|
-
One unconvertible schema
|
|
10241
|
+
One unconvertible schema does not take the connection down. The tool
|
|
10046
10242
|
is skipped and **named** (`onSkippedTool`, or a stderr line by default), so a
|
|
10047
10243
|
surface of two hundred tools is not lost to one.
|
|
10048
10244
|
|
|
@@ -10075,13 +10271,12 @@ credential, body, identity and stack stay with the original in-process cause in
|
|
|
10075
10271
|
Unknown throws remain `INTERNAL_SERVER_ERROR`. A timeout is not proof that a
|
|
10076
10272
|
write did not happen: verify the remote outcome before repeating an operation.
|
|
10077
10273
|
|
|
10078
|
-
**A remote refusal keeps its code.** A failed `tools/call`
|
|
10079
|
-
|
|
10080
|
-
the
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
A structured `{ error, details }` body is now relayed as the contract error it
|
|
10274
|
+
**A remote refusal keeps its code.** A failed `tools/call` must keep three things:
|
|
10275
|
+
the code (so `exitCodes` can map it instead of every remote failure exiting `1`),
|
|
10276
|
+
the message the operator needs, and the error's own class — a plain `Error` is
|
|
10277
|
+
an *unexpected* error to the runner, which would print a code frame of the framework
|
|
10278
|
+
bundle before the JSON failure and scrub it to `INTERNAL_SERVER_ERROR`.
|
|
10279
|
+
A structured `{ error, details }` body is relayed as the contract error it
|
|
10085
10280
|
is, on every transport; anything else fails as `UPSTREAM_TOOL_ERROR` carrying
|
|
10086
10281
|
what the server did send. Not `INTERNAL_SERVER_ERROR`, because nothing of ours
|
|
10087
10282
|
broke.
|
|
@@ -11230,15 +11425,6 @@ watch client; silent lost notifications are repaired by hub reconciliation.
|
|
|
11230
11425
|
# Guide: Live data (docs/guide/live.md)
|
|
11231
11426
|
==============================================================================
|
|
11232
11427
|
|
|
11233
|
-
---
|
|
11234
|
-
title: Live data — announcements, watched reads, keyspaces and the trust fence
|
|
11235
|
-
description: How to declare events beside the operation contract, share one server-side read between every browser asking the same question, keep authoritative memory in front of a durable backend, and refuse a request whose authority you never agreed to answer on.
|
|
11236
|
-
type: guide
|
|
11237
|
-
status: active
|
|
11238
|
-
created: 2026-09-02
|
|
11239
|
-
updated: 2026-09-02
|
|
11240
|
-
---
|
|
11241
|
-
|
|
11242
11428
|
# Live data
|
|
11243
11429
|
|
|
11244
11430
|
Four things an application builds by hand the second time it needs them.
|
|
@@ -11650,11 +11836,11 @@ Every refusal answers the same bare 403. A response that said *which* rule
|
|
|
11650
11836
|
refused would let a caller learn your trusted list one guess at a time; the
|
|
11651
11837
|
reason goes to `onRefused` and the log, where you are.
|
|
11652
11838
|
|
|
11653
|
-
###
|
|
11839
|
+
### Group hooks
|
|
11654
11840
|
|
|
11655
|
-
A route group
|
|
11656
|
-
|
|
11657
|
-
|
|
11841
|
+
A route group cannot declare `onRequest`: the framework dispatches only the
|
|
11842
|
+
server-level hook, and a group hook would need group prefixes matched a second
|
|
11843
|
+
time, ahead of the real router. Use the
|
|
11658
11844
|
server-level `hooks.onRequest` to refuse before dispatch, or the group's
|
|
11659
11845
|
`authorize` to gate once the endpoint is known.
|
|
11660
11846
|
|
|
@@ -12329,7 +12515,7 @@ whether the code declares `details`:
|
|
|
12329
12515
|
|
|
12330
12516
|
The model-facing envelope is `{ error, retryable, details?, _hint? }`; for a code with no
|
|
12331
12517
|
details schema the framework fills `details` with `{ message }`, so declaring a
|
|
12332
|
-
message changes what the model reads there
|
|
12518
|
+
message changes what the model reads there; without one it is the code itself. Put
|
|
12333
12519
|
anything the model must reliably read in `details` or `hint`, not in `message`.
|
|
12334
12520
|
|
|
12335
12521
|
With no `details` schema, the options object forbids `details`. A required
|
|
@@ -12842,6 +13028,32 @@ Without an `observability` the work still runs inside a context, so trace ids,
|
|
|
12842
13028
|
decision from having a context. `traceparent` continues the trace of whatever
|
|
12843
13029
|
scheduled the work, instead of starting an unrelated one.
|
|
12844
13030
|
|
|
13031
|
+
### CPU is not attributed to work
|
|
13032
|
+
|
|
13033
|
+
Request rows, `runUnitOfWork` rows and schedule status carry durations and
|
|
13034
|
+
counts, never CPU. One JavaScript thread interleaves every operation, so a
|
|
13035
|
+
`process.cpuUsage()` delta across an operation's start and end measures
|
|
13036
|
+
everything the process ran in that time — a long wait reads as the CPU of the
|
|
13037
|
+
timers that fired during it. The correct basis is the synchronous segment, and
|
|
13038
|
+
Bun gives no callback when a continuation is entered or left
|
|
13039
|
+
([ADR 0244](../decisions/0244-cpu-is-not-attributed-to-operations.md)).
|
|
13040
|
+
|
|
13041
|
+
Process CPU per window is the number you can observe:
|
|
13042
|
+
|
|
13043
|
+
```ts
|
|
13044
|
+
let last = process.cpuUsage()
|
|
13045
|
+
setInterval(() => {
|
|
13046
|
+
const now = process.cpuUsage() // cumulative { user, system } in µs
|
|
13047
|
+
logger.info('cpu', { cpuUserUs: now.user - last.user, cpuSystemUs: now.system - last.system })
|
|
13048
|
+
last = now
|
|
13049
|
+
}, 60_000)
|
|
13050
|
+
```
|
|
13051
|
+
|
|
13052
|
+
If you build attribution yourself: credit an operation only the CPU of the
|
|
13053
|
+
synchronous segments it ran, never its waiting time; put everything outside those
|
|
13054
|
+
segments into `unattributed`; never credit a window to the one operation in
|
|
13055
|
+
flight — that operation is usually the one waiting.
|
|
13056
|
+
|
|
12845
13057
|
### Write the calls that changed something
|
|
12846
13058
|
|
|
12847
13059
|
`auditChanges` is the filter most projects end up writing, shipped so they do not
|
|
@@ -12916,8 +13128,8 @@ needed:
|
|
|
12916
13128
|
createServer({ services, logging, observability: observability.request })
|
|
12917
13129
|
```
|
|
12918
13130
|
|
|
12919
|
-
`wrapInRequestContext`
|
|
12920
|
-
|
|
13131
|
+
`wrapInRequestContext` serves a custom fetch pipeline that does not use
|
|
13132
|
+
`createHandler`; built-in HTTP audit wiring does not use it:
|
|
12921
13133
|
|
|
12922
13134
|
```ts
|
|
12923
13135
|
Bun.serve({ fetch: wrapInRequestContext(customFetch) })
|
|
@@ -13254,9 +13466,9 @@ which the built-in **tool** row does not read: a tool event takes
|
|
|
13254
13466
|
`errorCode` / `errorMessage` / `errorDetail` from the `ToolResult`, and only
|
|
13255
13467
|
identity and `dimensions` from the context. Calling it in `onToolError` would
|
|
13256
13468
|
leave the tool row exactly as scrubbed as before. It is right for the **HTTP**
|
|
13257
|
-
path, where the request *is* the record. (
|
|
13258
|
-
|
|
13259
|
-
|
|
13469
|
+
path, where the request *is* the record. (A tool call runs in its own context, ADR 0045,
|
|
13470
|
+
so it cannot write into the enclosing `/mcp` request's row either — the call is
|
|
13471
|
+
simply not where that helper belongs.)
|
|
13260
13472
|
|
|
13261
13473
|
### One row that names the cause
|
|
13262
13474
|
|
|
@@ -14107,15 +14319,6 @@ per-transport tenant plumbing.
|
|
|
14107
14319
|
# Guide: Frontend integrations (docs/guide/frontend-integrations.md)
|
|
14108
14320
|
==============================================================================
|
|
14109
14321
|
|
|
14110
|
-
---
|
|
14111
|
-
title: Frontend integrations
|
|
14112
|
-
description: Compose Stitchkit with Next.js, React Router or a separate Vite development server
|
|
14113
|
-
type: architecture
|
|
14114
|
-
status: active
|
|
14115
|
-
created: 2026-08-08
|
|
14116
|
-
updated: 2026-08-08
|
|
14117
|
-
---
|
|
14118
|
-
|
|
14119
14322
|
# Frontend integrations
|
|
14120
14323
|
|
|
14121
14324
|
The official `bun create stitchkit` application uses Next.js with a separate
|
|
@@ -14177,15 +14380,6 @@ shared contract remains the only transport schema source.
|
|
|
14177
14380
|
# Guide: React Query policy (docs/guide/react.md)
|
|
14178
14381
|
==============================================================================
|
|
14179
14382
|
|
|
14180
|
-
---
|
|
14181
|
-
title: React Query policy
|
|
14182
|
-
description: One request-local server client, one browser singleton and explicit retry rules for Stitchkit API errors
|
|
14183
|
-
type: guide
|
|
14184
|
-
status: active
|
|
14185
|
-
created: 2026-09-06
|
|
14186
|
-
updated: 2026-09-06
|
|
14187
|
-
---
|
|
14188
|
-
|
|
14189
14383
|
# React Query policy
|
|
14190
14384
|
|
|
14191
14385
|
`stitchkit/react` contains policy adapters for TanStack Query. It does not own
|
|
@@ -14240,15 +14434,6 @@ endpoint or cookie vocabulary.
|
|
|
14240
14434
|
# Guide: Keyboard layers and list zones (docs/guide/keyboard.md)
|
|
14241
14435
|
==============================================================================
|
|
14242
14436
|
|
|
14243
|
-
---
|
|
14244
|
-
title: Keyboard layers and list zones
|
|
14245
|
-
description: One listener decides which part of a screen gets a key; lists move real focus with the arrows
|
|
14246
|
-
type: guide
|
|
14247
|
-
status: active
|
|
14248
|
-
created: 2026-09-29
|
|
14249
|
-
updated: 2026-09-29
|
|
14250
|
-
---
|
|
14251
|
-
|
|
14252
14437
|
# Keyboard layers and list zones
|
|
14253
14438
|
|
|
14254
14439
|
`stitchkit/react/keyboard` answers one question for a React screen: **who gets
|
|
@@ -15112,8 +15297,8 @@ the press was forged (a client can send any bytes) and goes to `onStale`.
|
|
|
15112
15297
|
|
|
15113
15298
|
A press on a message the record does not know — an older menu higher up — is
|
|
15114
15299
|
answered in place: a one-message view takes that message over, a longer view is
|
|
15115
|
-
sent below it and the pressed message is deleted. Either way the
|
|
15116
|
-
|
|
15300
|
+
sent below it and the pressed message is deleted. Either way the recorded
|
|
15301
|
+
view is removed: one live menu per chat.
|
|
15117
15302
|
|
|
15118
15303
|
For a message the screens do not own — a notification from the backend —
|
|
15119
15304
|
`screens.button(link('🔑 Refresh token', refreshToken, { botId }))` makes a
|
|
@@ -15227,15 +15412,6 @@ an incoming message to HTML, and a history stack.
|
|
|
15227
15412
|
# Guide: GeoIP (docs/guide/geo.md)
|
|
15228
15413
|
==============================================================================
|
|
15229
15414
|
|
|
15230
|
-
---
|
|
15231
|
-
title: GeoIP
|
|
15232
|
-
description: A server-only managed GeoIP reader with three observable states and last-known-good generation reloads.
|
|
15233
|
-
type: guide
|
|
15234
|
-
status: active
|
|
15235
|
-
created: 2026-09-06
|
|
15236
|
-
updated: 2026-09-06
|
|
15237
|
-
---
|
|
15238
|
-
|
|
15239
15415
|
# GeoIP
|
|
15240
15416
|
|
|
15241
15417
|
`stitchkit/geo` is a server-only, evolving entrypoint for reading local GeoIP
|
|
@@ -15319,6 +15495,413 @@ analytics policy belong to the application.
|
|
|
15319
15495
|
|
|
15320
15496
|
# Upgrading stitchkit
|
|
15321
15497
|
|
|
15498
|
+
## Released migration: 0.105.0
|
|
15499
|
+
|
|
15500
|
+
### CLI publication has its own entrypoint
|
|
15501
|
+
|
|
15502
|
+
**Who must act:** code that imports `publishCli`, `CliPublicationOptions`, `CliPublicationPhase` or
|
|
15503
|
+
`CliPublicationResult` from `stitchkit/cli`. Everything else in `stitchkit/cli` — `createCli`, the
|
|
15504
|
+
manifest schemas, signatures, `renderCliInstaller` and the updater — is unchanged.
|
|
15505
|
+
|
|
15506
|
+
`publishCli` is release infrastructure, so it now ships from the evolving entrypoint
|
|
15507
|
+
`stitchkit/cli/publish` instead of the stable `stitchkit/cli`
|
|
15508
|
+
([ADR 0245](../decisions/0245-cli-publication-is-an-evolving-leaf.md)). Its options, result and
|
|
15509
|
+
behaviour are the same.
|
|
15510
|
+
|
|
15511
|
+
1. Move the publication names to the new specifier; keep every other CLI import where it is:
|
|
15512
|
+
|
|
15513
|
+
```ts
|
|
15514
|
+
// before
|
|
15515
|
+
import { publishCli, type CliPublicationOptions, verifyCliManifest } from 'stitchkit/cli'
|
|
15516
|
+
// after
|
|
15517
|
+
import { verifyCliManifest } from 'stitchkit/cli'
|
|
15518
|
+
import { publishCli, type CliPublicationOptions } from 'stitchkit/cli/publish'
|
|
15519
|
+
```
|
|
15520
|
+
|
|
15521
|
+
Search pattern: `\b(publishCli|CliPublication(Options|Phase|Result))\b` in files that import
|
|
15522
|
+
from `'stitchkit/cli'`.
|
|
15523
|
+
2. Type-check: an import left on `stitchkit/cli` fails with "has no exported member".
|
|
15524
|
+
3. `stitchkit/cli/publish` is evolving: read its breaking entries in each minor you upgrade across.
|
|
15525
|
+
|
|
15526
|
+
### a command stops by its `stop` policy
|
|
15527
|
+
|
|
15528
|
+
**Who must act:** callers of `runNativeCommand` (`stitchkit/process`) that pass `killGraceMs`.
|
|
15529
|
+
Callers that omit it need no change: the default stop is the same TERM to the group, 100 ms, KILL.
|
|
15530
|
+
|
|
15531
|
+
1. Replace `killGraceMs: N` with a group stop of the same grace:
|
|
15532
|
+
|
|
15533
|
+
```ts
|
|
15534
|
+
// before
|
|
15535
|
+
await runNativeCommand({ executable, args, signal, killGraceMs: 500 })
|
|
15536
|
+
// after
|
|
15537
|
+
await runNativeCommand({ executable, args, signal, stop: { target: 'group', graceMs: 500 } })
|
|
15538
|
+
```
|
|
15539
|
+
|
|
15540
|
+
Search pattern: `killGraceMs:\s*(\d[\d_]*)` → `stop: { target: 'group', graceMs: $1 }`.
|
|
15541
|
+
2. If your own wrapper signals only the leader and waits a long grace (cooperative cancellation),
|
|
15542
|
+
replace it with `stop: { target: 'leader', signal: 'SIGINT', graceMs }` (up to one hour). Pass
|
|
15543
|
+
`killOn` with your shutdown signal so an application that stops sooner kills the group first.
|
|
15544
|
+
3. The result of a stopped command rejects once the group is gone, up to `graceMs` plus
|
|
15545
|
+
`cleanupTimeoutMs` after the abort; size your caller deadlines on that sum.
|
|
15546
|
+
|
|
15547
|
+
### a command stops what its leader left
|
|
15548
|
+
|
|
15549
|
+
**Who must act:** callers of `runNativeCommand` (`stitchkit/process`) whose command starts a helper
|
|
15550
|
+
in its own process group that must keep running after the command returns. A daemon that detaches
|
|
15551
|
+
with `setsid` (as `daemon(3)` does) is not in the group and needs
|
|
15552
|
+
no change.
|
|
15553
|
+
|
|
15554
|
+
`descendants` now defaults to `'terminate-after-leader'`
|
|
15555
|
+
([ADR 0246](../decisions/0246-a-command-stops-its-descendants-by-default.md)): when the leader
|
|
15556
|
+
exits, the members it left in the group are stopped by the command's `stop` policy before the
|
|
15557
|
+
pipes drain.
|
|
15558
|
+
|
|
15559
|
+
1. Find the commands that start a background helper on purpose (a `&` in a shell script, a spawned
|
|
15560
|
+
child the leader does not wait for) and declare `'leave'` on them:
|
|
15561
|
+
|
|
15562
|
+
```ts
|
|
15563
|
+
// before
|
|
15564
|
+
await runNativeCommand({ executable, args, timeoutMs })
|
|
15565
|
+
// after
|
|
15566
|
+
await runNativeCommand({ executable, args, timeoutMs, descendants: 'leave' })
|
|
15567
|
+
```
|
|
15568
|
+
|
|
15569
|
+
2. Remove an explicit `descendants: 'terminate-after-leader'`, or keep it: it is now the default.
|
|
15570
|
+
3. A command whose helper inherited stdout or stderr used to wait for that helper until its
|
|
15571
|
+
deadline. It now completes when the leader exits; drop a workaround that killed the helper from
|
|
15572
|
+
`onLeaderSettled`.
|
|
15573
|
+
|
|
15574
|
+
### native packaging verifies published digests
|
|
15575
|
+
|
|
15576
|
+
**Who must act:** build scripts that call `createNativePackaging` (`stitchkit/files/packaging`) and
|
|
15577
|
+
read `asset.sourcePath`, hash the addon themselves, switch over the refusal `state`/`code`, pass a
|
|
15578
|
+
`platform` other than the literal `'darwin'`, or parse `node_modules/stitchkit/native-assets.json`.
|
|
15579
|
+
|
|
15580
|
+
The package now publishes the `size` and SHA256 of each Darwin addon, computed when Stitchkit is
|
|
15581
|
+
built, in `native-assets.json` at `formatVersion: 2`. `createNativePackaging` reads each selected
|
|
15582
|
+
addon once, compares it with that digest and returns the verified bytes. An installed addon that
|
|
15583
|
+
differs refuses with `NATIVE_ASSET_DIGEST_MISMATCH`.
|
|
15584
|
+
|
|
15585
|
+
1. Replace every read of `asset.sourcePath` with `asset.bytes`, and delete your own re-hash of the
|
|
15586
|
+
addon: the result's `sha256` is the published digest and `bytes` already match it.
|
|
15587
|
+
|
|
15588
|
+
```ts
|
|
15589
|
+
// before
|
|
15590
|
+
const bytes = readFileSync(asset.sourcePath)
|
|
15591
|
+
if (createHash('sha256').update(bytes).digest('hex') !== asset.sha256) throw …
|
|
15592
|
+
copyFileSync(asset.sourcePath, destination)
|
|
15593
|
+
// after
|
|
15594
|
+
writeFileSync(destination, asset.bytes)
|
|
15595
|
+
```
|
|
15596
|
+
|
|
15597
|
+
2. Handle the third refusal. A `switch` over `result.state` gains `'mismatch'`
|
|
15598
|
+
(`code: 'NATIVE_ASSET_DIGEST_MISMATCH'`); treat it as a failed build, never as a missing addon.
|
|
15599
|
+
It carries the differing `architecture` with `expected` (published) and `actual` (installed)
|
|
15600
|
+
`{ size, sha256 }`; log both pairs.
|
|
15601
|
+
3. Pass the target platform as the literal `'darwin'`. `platform` is a closed enum, so a free
|
|
15602
|
+
string (a target name read from configuration, `process.platform`) no longer type-checks, and
|
|
15603
|
+
another platform throws instead of returning `unsupported`. Skip packaging for non-Darwin
|
|
15604
|
+
targets before the call:
|
|
15605
|
+
|
|
15606
|
+
```ts
|
|
15607
|
+
// before
|
|
15608
|
+
const native = createNativePackaging({ platform: target.platform, architecture, … })
|
|
15609
|
+
if (native.state === 'unsupported') return
|
|
15610
|
+
// after
|
|
15611
|
+
if (target.platform !== 'darwin') return
|
|
15612
|
+
const native = createNativePackaging({ platform: target.platform, architecture, … })
|
|
15613
|
+
```
|
|
15614
|
+
|
|
15615
|
+
`unsupported` now means only an architecture the package has no addon for.
|
|
15616
|
+
4. Call `createNativePackaging` once per build and pass the result (its `assets` and `plugin`)
|
|
15617
|
+
where it is needed. Each call reads and hashes the selected addons again.
|
|
15618
|
+
5. If you read `native-assets.json` directly, read `assets.<arch>.path`, `.size` and `.sha256`
|
|
15619
|
+
instead of a bare path per architecture; an architecture with no entry is not published.
|
|
15620
|
+
|
|
15621
|
+
### exclusive locks publish their owner first
|
|
15622
|
+
|
|
15623
|
+
**Who must act:** callers of `withExclusiveLock` (`stitchkit/files`) that rely on a lock file with no
|
|
15624
|
+
readable owner being taken by age, and anything that puts lock files on a filesystem without hard
|
|
15625
|
+
links. Every other caller needs no change.
|
|
15626
|
+
|
|
15627
|
+
A lock is now written to a private `.lock-<id>.tmp` file in the lock's directory, then hard-linked to
|
|
15628
|
+
the lock name. The name never exists without a complete owner record, so `ownerlessGraceMs` defaults
|
|
15629
|
+
to `null` instead of 5 000 ms: a lock file with no readable owner is never taken because of its age.
|
|
15630
|
+
|
|
15631
|
+
1. If you depended on age-based reclaim of an empty lock file left by an older writer, opt in:
|
|
15632
|
+
|
|
15633
|
+
```ts
|
|
15634
|
+
// before: an empty lock older than 5 s was taken by default
|
|
15635
|
+
await withExclusiveLock(path, run)
|
|
15636
|
+
// after: say so; only legacy empty files need it
|
|
15637
|
+
await withExclusiveLock(path, run, { ownerlessGraceMs: 5_000 })
|
|
15638
|
+
```
|
|
15639
|
+
|
|
15640
|
+
2. Keep lock directories on a filesystem that supports hard links (local ext4, xfs, APFS and the
|
|
15641
|
+
like). `link` failing with `EPERM` or `ENOTSUP` fails the acquisition.
|
|
15642
|
+
3. Expect a `.lock-*.tmp` sibling only after a holder was killed between writing its record and
|
|
15643
|
+
publishing it. It is not a lock; the next reclaim in that directory removes the ones whose owner
|
|
15644
|
+
is gone on this machine. Exclude the pattern from directory-size caps or backups if you have any.
|
|
15645
|
+
|
|
15646
|
+
### one runtime-tool declaration
|
|
15647
|
+
|
|
15648
|
+
**Who must act:** code that names `RuntimeToolExecution`, `RuntimeToolExecutionWithOutput`,
|
|
15649
|
+
`RuntimeMcpToolDefinition`, `RuntimeMcpToolDefinitionWithOutput` or `ToolSurfaceDefinition`; code
|
|
15650
|
+
that annotates a definition carrying `present` as `RuntimeToolDefinitionWithOutput<I, O>` from
|
|
15651
|
+
`stitchkit/tools`; imports of `stitchkit/agent-runtime/harness-tools`; code that compares agent
|
|
15652
|
+
control error codes; and callers that send a JSON array as text to a string-or-array tool field.
|
|
15653
|
+
Definitions returned by `defineRuntimeTool` / `createRuntimeToolFactory`, native CLI `commands` and
|
|
15654
|
+
contract tools need no change.
|
|
15655
|
+
|
|
15656
|
+
A runtime tool now has one declaration, `RuntimeToolDefinitionWithOutput` /
|
|
15657
|
+
`RuntimeToolDefinitionWithoutOutput`, exported with the same name from `stitchkit/cli`,
|
|
15658
|
+
`stitchkit/tools` and `stitchkit/tools/mcp`. It carries no SDK type: the presenters are the adapter's
|
|
15659
|
+
extension, named as the fourth type argument. `RuntimeToolDefinition` is the one registration type.
|
|
15660
|
+
|
|
15661
|
+
1. Replace each removed name.
|
|
15662
|
+
|
|
15663
|
+
| before | after |
|
|
15664
|
+
|---|---|
|
|
15665
|
+
| `RuntimeToolExecutionWithOutput<I, O>` (`stitchkit/cli`) | `RuntimeToolDefinitionWithOutput<I, O>` |
|
|
15666
|
+
| `RuntimeToolExecution` (`stitchkit/cli`) | `RuntimeToolDefinition` |
|
|
15667
|
+
| `RuntimeMcpToolDefinitionWithOutput<I, O>` (`stitchkit/tools/mcp`) | `RuntimeToolDefinitionWithOutput<I, O, undefined, RuntimeMcpToolPresenters<z.output<O>>>` |
|
|
15668
|
+
| `RuntimeMcpToolDefinition` (`stitchkit/tools/mcp`) | `RuntimeToolDefinition` |
|
|
15669
|
+
| `ToolSurfaceDefinition` (`stitchkit/tools`) | `ToolSurfaceProjection<RuntimeToolDefinition>` |
|
|
15670
|
+
|
|
15671
|
+
2. In `stitchkit/tools`, a type that names a definition with presenters passes the presenter type:
|
|
15672
|
+
|
|
15673
|
+
```ts
|
|
15674
|
+
// before
|
|
15675
|
+
const tool: RuntimeToolDefinitionWithOutput<typeof input, typeof output> = defineRuntimeTool({ …, present })
|
|
15676
|
+
// after: let defineRuntimeTool infer it, or name the presenters
|
|
15677
|
+
const tool = defineRuntimeTool({ …, present })
|
|
15678
|
+
const named: RuntimeToolDefinitionWithOutput<typeof input, typeof output, undefined, RuntimeToolPresenters<z.output<typeof output>>> = tool
|
|
15679
|
+
```
|
|
15680
|
+
|
|
15681
|
+
3. An inline handler in `runtimeTools: [...]` needs no wrapper: it is typed by the parsed object.
|
|
15682
|
+
To type `input` from the schema, construct with `defineRuntimeTool` (or
|
|
15683
|
+
`satisfies RuntimeToolDefinitionWithOutput<typeof input, typeof output>`) before registering.
|
|
15684
|
+
|
|
15685
|
+
4. Replace `createAgentHarnessTools` (`stitchkit/agent-runtime/harness-tools`) with the call it made:
|
|
15686
|
+
|
|
15687
|
+
```ts
|
|
15688
|
+
// before
|
|
15689
|
+
tools: createAgentHarnessTools(({ context }) => ({ services: [svc], context, lifecycle: auth }))
|
|
15690
|
+
// after
|
|
15691
|
+
import { composeToolLifecycle, mountAgent } from 'stitchkit/tools'
|
|
15692
|
+
tools: ({ context, toolFenceLifecycle }) =>
|
|
15693
|
+
mountAgent([svc], { context, lifecycle: composeToolLifecycle(auth, toolFenceLifecycle) })
|
|
15694
|
+
```
|
|
15695
|
+
|
|
15696
|
+
5. Agent control codes: `ACCESS_DENIED` is `FORBIDDEN`, and `CONNECTION_CLOSED` is returned for a closed
|
|
15697
|
+
connection. `AgentControlErrorCode` lists every code. `authorize` now runs once per request; after you
|
|
15698
|
+
withdraw someone's access, call `binding.revoke(conversationId, matches?)` so an open stream is
|
|
15699
|
+
re-authorized and ends with an `access-denied` delivery.
|
|
15700
|
+
|
|
15701
|
+
6. A string-or-array field that receives `'["a","b"]'` now fails with `VALIDATION_ERROR` ("`paths` is a
|
|
15702
|
+
list written as text"). Send the array itself. A string that is not a list the array member accepts
|
|
15703
|
+
(`[preview].png`) is still one plain string.
|
|
15704
|
+
|
|
15705
|
+
### flattened unions keep declaration order
|
|
15706
|
+
|
|
15707
|
+
**Who must act:** consumers that use `flattenUnionInput: true` and keep snapshot tests, golden
|
|
15708
|
+
files or provider prompt caches of flattened tool schemas.
|
|
15709
|
+
|
|
15710
|
+
A flattened union now lists its properties, enum values and branch labels in the order they first
|
|
15711
|
+
appear across the variants, not alphabetically. Declaration order often encodes priority, so the
|
|
15712
|
+
model now reads the order you wrote. Alternatives of different kinds (`anyOf`) keep their
|
|
15713
|
+
canonical order, independent of variant order.
|
|
15714
|
+
|
|
15715
|
+
1. Regenerate every snapshot or golden file of a flattened tool listing (`buildToolManifest`,
|
|
15716
|
+
`listToolNames`, MCP `tools/list`, Agent tool schemas) and review the diff: only ordering moves.
|
|
15717
|
+
2. Expect one provider prompt-cache miss per flattened tool after the upgrade.
|
|
15718
|
+
3. To keep an order you prefer, declare the variants and fields in that order.
|
|
15719
|
+
|
|
15720
|
+
### connection limits are validated
|
|
15721
|
+
|
|
15722
|
+
**Who must act:** callers that pass `timeoutMs` or `maxResponseBytes` to a connection and relied
|
|
15723
|
+
on an invalid value silently becoming the default.
|
|
15724
|
+
|
|
15725
|
+
`stitchkit/tools/connections` throws a `RangeError` when `timeoutMs` is not a positive integer
|
|
15726
|
+
within timer range (at most 2 147 483 647) or `maxResponseBytes` is not a positive safe integer.
|
|
15727
|
+
Before 0.104.0 a value of 0, a negative number, a fraction or an out-of-range number fell back to
|
|
15728
|
+
the default without a message.
|
|
15729
|
+
|
|
15730
|
+
```ts
|
|
15731
|
+
// Before: { timeoutMs: 0 } meant "use the default" by accident.
|
|
15732
|
+
// After: omit the option to take the default, or pass a real limit.
|
|
15733
|
+
{ timeoutMs: 30_000 }
|
|
15734
|
+
```
|
|
15735
|
+
|
|
15736
|
+
### one canonical JSON serializer
|
|
15737
|
+
|
|
15738
|
+
**Who must act:** tests that call `serializeSurfaceValue` from `stitchkit/testing`, and code that
|
|
15739
|
+
puts a value nested deeper than 100 levels into agent store events, archives or projections
|
|
15740
|
+
through `canonicalAgentJson`.
|
|
15741
|
+
|
|
15742
|
+
`canonicalJson` from `stitchkit/primitives` is the package's one canonical serializer. It
|
|
15743
|
+
admits dense arrays, plain objects, strings, booleans, `null` and finite numbers; an `undefined`
|
|
15744
|
+
object member is omitted. It refuses everything else with a `CanonicalJsonError`, a `TypeError`
|
|
15745
|
+
whose `reason` says why: `'depth'`, `'nodes'` and `'bytes'` are the limits you set, while
|
|
15746
|
+
`'cycle'`, `'negative-zero'` and `'not-json'` say the value is not JSON data.
|
|
15747
|
+
|
|
15748
|
+
`serializeSurfaceValue` is removed. It produced the same bytes without limits:
|
|
15749
|
+
|
|
15750
|
+
```ts
|
|
15751
|
+
// Before
|
|
15752
|
+
import { serializeSurfaceValue } from 'stitchkit/testing'
|
|
15753
|
+
const text = serializeSurfaceValue(manifest)
|
|
15754
|
+
// After: the limits default to 100 levels, 100 000 nodes and 1 MiB; raise them for a large manifest
|
|
15755
|
+
import { canonicalJson } from 'stitchkit/primitives'
|
|
15756
|
+
const text = canonicalJson(manifest, { maxBytes: 16 * 1024 * 1024 })
|
|
15757
|
+
```
|
|
15758
|
+
|
|
15759
|
+
`canonicalAgentJson` in `stitchkit/agent-runtime` is `canonicalJson` with the node and byte
|
|
15760
|
+
limits at their maximum. A value both accepted has the same bytes as before, so stored digests
|
|
15761
|
+
and archives still match: like `z.json()` before it, the store hashes `-0` as `0` and drops an
|
|
15762
|
+
own `__proto__` member (the public `canonicalJson` refuses `-0` and keeps `__proto__`). What
|
|
15763
|
+
changed is the refusal table: an `undefined` object member used to fail and is now omitted, and
|
|
15764
|
+
a value nested deeper than 100 levels is now refused.
|
|
15765
|
+
|
|
15766
|
+
```ts
|
|
15767
|
+
// Before: canonicalAgentJson({ a: undefined }) threw a ZodError
|
|
15768
|
+
// After: canonicalAgentJson({ a: undefined }) === '{}'
|
|
15769
|
+
// canonicalAgentJson(nestedBeyond100Levels) throws CanonicalJsonError (reason 'depth')
|
|
15770
|
+
```
|
|
15771
|
+
|
|
15772
|
+
### directory inbox entries
|
|
15773
|
+
|
|
15774
|
+
**Who must act:** producers that drop hard links into an inbox directory, and callers that pass
|
|
15775
|
+
`accept` the output of a transforming schema.
|
|
15776
|
+
|
|
15777
|
+
A file dropped into the inbox directory with more than one link is set aside as `invalid`
|
|
15778
|
+
(detail `managed file has multiple links`). Another name can still rewrite such a file after it
|
|
15779
|
+
was validated, so it is never trusted. Write each entry as its own regular file: a temporary
|
|
15780
|
+
name in the same directory, then a rename onto `<name>.json`.
|
|
15781
|
+
|
|
15782
|
+
`accept` takes the schema's input type, stores that input as given and parses it with the
|
|
15783
|
+
schema on delivery. A schema without a transform has the same input and output type and needs
|
|
15784
|
+
no change. With a transform, pass the input:
|
|
15785
|
+
|
|
15786
|
+
```ts
|
|
15787
|
+
const schema = z.object({ amount: z.string().transform(Number) })
|
|
15788
|
+
// Before: accept({ source, key, entry: { amount: 42 } }) // typed as the output; `z.string()` refused it
|
|
15789
|
+
// After: accept({ source, key, entry: { amount: '42' } }) // delivered as { amount: 42 }
|
|
15790
|
+
```
|
|
15791
|
+
|
|
15792
|
+
A name that is already in `rejected/` no longer stops the pass: identical bytes are the entry
|
|
15793
|
+
already set aside, different bytes are kept as `rejected/<name>.<16 hex digits>`.
|
|
15794
|
+
|
|
15795
|
+
### journal startup scan
|
|
15796
|
+
|
|
15797
|
+
**Who must act:** operators that read `status.recovery` right after `createDiagnosticJournal` to
|
|
15798
|
+
find damaged rows in the middle of a file.
|
|
15799
|
+
|
|
15800
|
+
Opening a journal reads only the last line of the active file and of every retained
|
|
15801
|
+
generation (`startupScan: 'tails'`, the default). A crash tears the end of a file, so a torn tail
|
|
15802
|
+
is still reported and still rotated away, and opening no longer parses and validates every
|
|
15803
|
+
line of every generation. It still reads each file once in large blocks to count lines, so the time
|
|
15804
|
+
grows with the bytes retained, at read speed. Pass `startupScan: 'full'` to read and validate every line as before:
|
|
15805
|
+
|
|
15806
|
+
```ts
|
|
15807
|
+
// Before: every open parsed every line of every generation.
|
|
15808
|
+
// After: the same full scan, on request.
|
|
15809
|
+
const journal = await createDiagnosticJournal({ path, eventSchema, limits, startupScan: 'full' })
|
|
15810
|
+
```
|
|
15811
|
+
|
|
15812
|
+
### journal startup quarantine
|
|
15813
|
+
|
|
15814
|
+
**Who must act:** operators of a journal that is a source of truth and must not start over
|
|
15815
|
+
damaged retained files; code that constructs `DiagnosticJournalRecoveryError` or reads its
|
|
15816
|
+
`recovery`; protocols that embed `DiagnosticJournalRecoveryStatusSchema`, and their readers.
|
|
15817
|
+
A diagnostic journal needs no change.
|
|
15818
|
+
|
|
15819
|
+
Opening a journal no longer refuses to start over a torn active file with `maxFiles: 1`, a
|
|
15820
|
+
generation name that is not a regular file or a retained file it cannot read. It renames the file
|
|
15821
|
+
to `<file>.quarantined-<epoch>` beside the journal, starts, and lists the move in
|
|
15822
|
+
`getStatus().recovery.quarantined`. Quarantined files are outside retention and never deleted;
|
|
15823
|
+
every later open lists them again, without a `reason`, until you remove them.
|
|
15824
|
+
|
|
15825
|
+
1. A journal whose damage must stop the process declares the old behaviour:
|
|
15826
|
+
|
|
15827
|
+
```ts
|
|
15828
|
+
// Before: refusing was the only behaviour.
|
|
15829
|
+
await createDiagnosticJournal({ path, eventSchema, limits })
|
|
15830
|
+
// After:
|
|
15831
|
+
await createDiagnosticJournal({ path, eventSchema, limits, onStartupRefusal: 'fail' })
|
|
15832
|
+
```
|
|
15833
|
+
|
|
15834
|
+
2. Branch on the refusal's `reason` instead of its message or error class. Every startup refusal
|
|
15835
|
+
about retained evidence is a `DiagnosticJournalRecoveryError`, including the former plain
|
|
15836
|
+
`Error` for a non-file generation and the raw `EACCES` for an unreadable one (now in `cause`):
|
|
15837
|
+
|
|
15838
|
+
```ts
|
|
15839
|
+
// Before: catch (error) { if (error instanceof DiagnosticJournalRecoveryError) use(error.recovery) }
|
|
15840
|
+
// After:
|
|
15841
|
+
catch (error) {
|
|
15842
|
+
if (error instanceof DiagnosticJournalRecoveryError && error.reason === 'torn-without-retention-slot') use(error.recovery)
|
|
15843
|
+
}
|
|
15844
|
+
```
|
|
15845
|
+
|
|
15846
|
+
`recovery` is present only for `torn-without-retention-slot`. The constructor takes one
|
|
15847
|
+
options object: `new DiagnosticJournalRecoveryError({ reason, file, recovery?, cause? })`.
|
|
15848
|
+
|
|
15849
|
+
3. A protocol that embeds `DiagnosticJournalRecoveryStatusSchema` carries the new optional
|
|
15850
|
+
`quarantined` array (at most 32 entries, `reason` optional) and `quarantinedUnlisted` count.
|
|
15851
|
+
The schema is `.strict()`: upgrade every reader that parses it with its own copy before a
|
|
15852
|
+
writer on this version sends a status with either field, or bump your protocol version.
|
|
15853
|
+
|
|
15854
|
+
### error constructors take named options
|
|
15855
|
+
|
|
15856
|
+
**Who must act:** code that constructs an `AppError` or an `ApiError` with anything beyond its
|
|
15857
|
+
code, and classes that extend `AppError` and call `super(code, message, status, …)`. Code that
|
|
15858
|
+
only throws the `notFound`, `badRequest`, `unauthorized`, `forbidden`, `conflict` and
|
|
15859
|
+
`rateLimited` helpers, or only reads `code`, `status`, `details`, `hint`, `traceId`,
|
|
15860
|
+
`retryable` and `cause` from a caught error, needs no change.
|
|
15861
|
+
|
|
15862
|
+
`AppError` (`stitchkit/contract`, `stitchkit/server`, `stitchkit/node`) and `ApiError`
|
|
15863
|
+
(`stitchkit`) keep the code as the one positional argument. Every other value is a named
|
|
15864
|
+
option, so a call site reads without the signature open. `new AppError(code)` and
|
|
15865
|
+
`new ApiError(code)` are unchanged. The compiler finds every old call: a string or number
|
|
15866
|
+
in the second position is a type error.
|
|
15867
|
+
|
|
15868
|
+
1. `AppError`: move `message`, `status`, `details`, `hint`, `traceId` and `retryable` into one
|
|
15869
|
+
object after the code. `status` defaults to 500 and `message` to the code.
|
|
15870
|
+
|
|
15871
|
+
```ts
|
|
15872
|
+
// before
|
|
15873
|
+
throw new AppError('CONFLICT', 'Selection is stale', 409, { revision: 7 })
|
|
15874
|
+
throw new AppError('BUSY', 'Try later', 503, undefined, undefined, undefined, true)
|
|
15875
|
+
// after
|
|
15876
|
+
throw new AppError('CONFLICT', { message: 'Selection is stale', status: 409, details: { revision: 7 } })
|
|
15877
|
+
throw new AppError('BUSY', { message: 'Try later', status: 503, retryable: true })
|
|
15878
|
+
```
|
|
15879
|
+
|
|
15880
|
+
2. A subclass passes the same object to `super`:
|
|
15881
|
+
|
|
15882
|
+
```ts
|
|
15883
|
+
// before
|
|
15884
|
+
super('FEATURE_LOCKED', 'locked', 403)
|
|
15885
|
+
// after
|
|
15886
|
+
super('FEATURE_LOCKED', { message: 'locked', status: 403 })
|
|
15887
|
+
```
|
|
15888
|
+
|
|
15889
|
+
3. `ApiError`: the old order was `code, status, details, message, hint, traceId, errorOptions,
|
|
15890
|
+
retryable`. Name each value; `status` defaults to 0 and the former `ErrorOptions` argument
|
|
15891
|
+
is now the `cause` option.
|
|
15892
|
+
|
|
15893
|
+
```ts
|
|
15894
|
+
// before
|
|
15895
|
+
new ApiError('RATE_LIMITED', 429, undefined, 'slow down', 'wait', 'trace-1', { cause }, true)
|
|
15896
|
+
new ApiError('VALIDATION_ERROR', 0, { issues }, 'bad input')
|
|
15897
|
+
// after
|
|
15898
|
+
new ApiError('RATE_LIMITED', { status: 429, message: 'slow down', hint: 'wait', traceId: 'trace-1', retryable: true, cause })
|
|
15899
|
+
new ApiError('VALIDATION_ERROR', { status: 0, details: { issues }, message: 'bad input' })
|
|
15900
|
+
```
|
|
15901
|
+
|
|
15902
|
+
4. Both classes accept the standard `cause`, which `error.cause` returns; omit the option to
|
|
15903
|
+
leave the property absent.
|
|
15904
|
+
|
|
15322
15905
|
## Released migration: 0.104.0
|
|
15323
15906
|
|
|
15324
15907
|
JS bundles carrying Darwin native capabilities also need the
|
|
@@ -15444,10 +16027,20 @@ that work actually completes.
|
|
|
15444
16027
|
|
|
15445
16028
|
### Darwin native assets in JS distributions
|
|
15446
16029
|
|
|
15447
|
-
**Who must act:** Bun
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
16030
|
+
**Who must act:** Bun builds that import `stitchkit/server`, `stitchkit/files` or
|
|
16031
|
+
`stitchkit/process` (Linux-only server apps included) and use `--outfile`, copy only
|
|
16032
|
+
`outputs[0]`, or package a single file; custom single-file/archive packagers and installers
|
|
16033
|
+
delivering native process/files capabilities.
|
|
16034
|
+
First affected publication: **0.103.13**, through **0.104.2**. It shipped as a patch; its
|
|
16035
|
+
changed delivery graph requires this migration despite unchanged API signatures.
|
|
16036
|
+
|
|
16037
|
+
In those versions every Bun bundle that reaches the lock or process-identity code,
|
|
16038
|
+
`stitchkit/server` included, carries both Darwin `.node` addons as extra outputs on every
|
|
16039
|
+
operating system. `bun build --outfile` then fails with
|
|
16040
|
+
`cannot write multiple output files without an output directory`, and `--outdir` writes the
|
|
16041
|
+
addons next to a Linux artifact. Releases after 0.104.2 resolve the default loader at run time,
|
|
16042
|
+
so these bundles are one JS file with no `.node` output again. The static loader exists only
|
|
16043
|
+
in a build that uses the `stitchkit/files/packaging` plugin.
|
|
15451
16044
|
|
|
15452
16045
|
```sh
|
|
15453
16046
|
# Before: one JS file was assumed to be the entire distribution.
|
|
@@ -15464,12 +16057,39 @@ Custom output layout uses the evolving build-only packaging contract in the
|
|
|
15464
16057
|
|
|
15465
16058
|
Three deliveries have different rules: an ordinary npm install retains the package's
|
|
15466
16059
|
native assets; a JS distribution must ship its complete output graph; a Bun compiled
|
|
15467
|
-
executable embeds the matching addon and may still use `--outfile`.
|
|
16060
|
+
executable embeds the matching addon and may still use `--outfile`. In 0.103.13–0.104.2 a plain
|
|
16061
|
+
`bun build` carries the addon without a plugin; later releases carry it only when the build
|
|
16062
|
+
uses `createNativePackaging` (`delivery: 'companion'` for JS, `'embedded'` for a compiled
|
|
16063
|
+
executable).
|
|
15468
16064
|
The [native IO guide](native-io.md#libraries-with-a-zod-only-runtime) is the canonical recipe.
|
|
15469
16065
|
Check the resulting artifact offline outside the build tree and `node_modules`, including
|
|
15470
16066
|
process identity, live-owner lock refusal, dead-owner recovery and contained IO.
|
|
15471
16067
|
A missing or corrupt addon remains `unavailable`, never evidence that an owner died.
|
|
15472
16068
|
|
|
16069
|
+
### CLI: repeated options are refused
|
|
16070
|
+
|
|
16071
|
+
**Who must act:** wrappers, aliases and scripts that append `--json`, `--profile X` or an
|
|
16072
|
+
application global to arguments the user may already have given, and anything that passes the
|
|
16073
|
+
same global twice.
|
|
16074
|
+
First affected publication: **0.103.13**. It shipped as a patch; the change in grammar needs
|
|
16075
|
+
this migration.
|
|
16076
|
+
|
|
16077
|
+
A repeated option that is not an array field is an argument error:
|
|
16078
|
+
`--json --json` and `--profile a --profile b` fail with `--json was passed 2 times`. Before
|
|
16079
|
+
0.103.13 the last value won, for framework globals, application globals and repeated
|
|
16080
|
+
`kind: 'other'` fields alike. Array fields, and untyped passthrough fields that keep their
|
|
16081
|
+
raw-list grammar, still repeat.
|
|
16082
|
+
|
|
16083
|
+
```sh
|
|
16084
|
+
# Before: a wrapper appended its default and the last value won.
|
|
16085
|
+
mytool deploy --profile staging "$@" --profile production
|
|
16086
|
+
# After: build the argument list once, so each option appears once.
|
|
16087
|
+
mytool deploy --profile "${PROFILE:-production}" "$@"
|
|
16088
|
+
```
|
|
16089
|
+
|
|
16090
|
+
Dotted leaves such as `--filter.name` follow the same rule as their parent field. Declare a
|
|
16091
|
+
field as an array when repetition is meant.
|
|
16092
|
+
|
|
15473
16093
|
## Released migration: 0.103.0
|
|
15474
16094
|
|
|
15475
16095
|
### Diagnostic journal startup preserves torn evidence and inspects archives
|
|
@@ -19257,15 +19877,6 @@ end of this file.
|
|
|
19257
19877
|
# Guide: Project declaration (docs/guide/declaration.md)
|
|
19258
19878
|
==============================================================================
|
|
19259
19879
|
|
|
19260
|
-
---
|
|
19261
|
-
title: Project declaration
|
|
19262
|
-
description: One machine-readable statement a repository makes about itself — identity, roles, build, requirements, release steps and the names of the values a deployment supplies.
|
|
19263
|
-
type: architecture
|
|
19264
|
-
status: active
|
|
19265
|
-
created: 2026-08-25
|
|
19266
|
-
updated: 2026-08-30
|
|
19267
|
-
---
|
|
19268
|
-
|
|
19269
19880
|
# Project declaration
|
|
19270
19881
|
|
|
19271
19882
|
> **Maturity: evolving.** This surface is still finding its shape and may be
|
|
@@ -19828,6 +20439,7 @@ schedules, transports and file generation remain application-owned.
|
|
|
19828
20439
|
|---|---|---|
|
|
19829
20440
|
| `canonicalJson` | function | bounded plain JSON, UTF-16 key sort, optional object members omitted |
|
|
19830
20441
|
| `CanonicalJsonOptions` | type | inferred depth/node/UTF-8 bytes limits |
|
|
20442
|
+
| `CanonicalJsonError` / `CanonicalJsonRefusal` | class / _type_ | the `TypeError` of a refusal and its `reason`: `cycle`, `negative-zero`, `depth`, `nodes`, `bytes` or `not-json` |
|
|
19831
20443
|
|
|
19832
20444
|
|
|
19833
20445
|
## `stitchkit/server`
|
|
@@ -19981,7 +20593,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
19981
20593
|
### Primitives
|
|
19982
20594
|
|
|
19983
20595
|
| `createFileStateStore` | function | cross-process locked atomic JSON state updates over one Zod schema |
|
|
19984
|
-
| `FileStateStoreCorruption` / `FileStateStoreOptions` | _type_ | explicit corrupt-state report/policy
|
|
20596
|
+
| `FileStateStoreCorruption` / `FileStateStoreOptions` | _type_ | explicit corrupt-state report/policy, lock timing and the state read cap `maxBytes`; a transition whose resulting state encodes to the bytes it started from writes nothing |
|
|
19985
20597
|
| Export | Kind | Summary |
|
|
19986
20598
|
|--------|------|---------|
|
|
19987
20599
|
| `streamSSE` | function | an async generator → SSE `Response` — [guide](../guide/server.md#sse-streaming) |
|
|
@@ -20087,7 +20699,7 @@ cutovers are covered by the executable
|
|
|
20087
20699
|
| `NotificationOutboxItem` / `NotificationOutboxReceipt` / `NotificationOutboxState` / `NotificationSend` / `NotificationFailureClassification` / `DroppedNotification` / `EnqueueNotification` | _type_ | versioned queue records, stable send identity and delivery decisions |
|
|
20088
20700
|
| `notificationOutboxStateSchema` | function | strict persisted-state schema over an application payload schema |
|
|
20089
20701
|
| `notificationOutboxResource` / `NotificationOutboxResource` / `NotificationOutboxResourceConfig` | function / _type_ | thin managed-resource scheduler over an outbox handle |
|
|
20090
|
-
| `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | one managed directory inbox for files or durable `accept({ source, key, entry })`; first payload wins, bounded capacity/bytes/receipts and renewable fenced leases; delivery begins after readiness and effects remain at least once |
|
|
20702
|
+
| `createDirectoryInbox` (from `stitchkit/application/directory-inbox`) / `DirectoryInbox` / `DirectoryInboxConfig` / `DirectoryInboxDelivery` / `DirectoryInboxResource` | function / _type_ | one managed directory inbox for files or durable `accept({ source, key, entry })` (`entry` is the schema's input, stored as given); a hard-linked file is set aside as `invalid`; first payload wins, bounded capacity/bytes/receipts and renewable fenced leases; delivery begins after readiness and effects remain at least once |
|
|
20091
20703
|
| `DirectoryInboxIdentitySchema` / `DirectoryInboxIdentity` / `DirectoryInboxAccept` / `DirectoryInboxAcceptResultSchema` / `DirectoryInboxAcceptResult` | schema / _type_ | validated source/key, typed entry and durable accepted/duplicate result with filename; duplicates do not replace the first payload |
|
|
20092
20704
|
| `StateStoreUpdateContext` | _type_ | real active protected transaction: `assertHeld()` refuses after ownership loss or completion; custom adapters supply this context to their transition |
|
|
20093
20705
|
| `DirectoryInboxStateSchema` / `DirectoryInboxState` / `DirectoryInboxRejection` / `DirectoryInboxRejectionReasonSchema` / `DirectoryInboxRejectionReason` | schema / _type_ | the inbox's claims, receipts and rejections (`invalid`, `too-large`, `attempt-limit`) |
|
|
@@ -20173,9 +20785,10 @@ cutovers are covered by the executable
|
|
|
20173
20785
|
| Export | Kind | Summary |
|
|
20174
20786
|
|--------|------|---------|
|
|
20175
20787
|
| `createDiagnosticJournal` | function | create one schema-owned FIFO JSONL writer with bounded retained memory, exclusive local path ownership and finite rotation |
|
|
20176
|
-
| `DiagnosticJournalConfig` / `DiagnosticJournal` | _type_ | owner schema/path/limits/failure observer and the synchronous `submit`, bounded-wait `flush`/`close`, status handle |
|
|
20788
|
+
| `DiagnosticJournalConfig` / `DiagnosticJournal` | _type_ | owner schema/path/limits/failure observer, `startupScan` (`tails` by default, or `full`), `onStartupRefusal` (`quarantine` by default, or `fail`) and the synchronous `submit`, bounded-wait `flush`/`close`, status handle |
|
|
20177
20789
|
| `DiagnosticJournalLimitsSchema` / `DiagnosticJournalLimits` | schema / _type_ | positive event, pending-item, pending-byte, file-byte and retained-file limits |
|
|
20178
20790
|
| `DiagnosticJournalLockPolicySchema` / `DiagnosticJournalLockPolicy` | schema / _type_ | `refuse` (default) or `reclaim-stale`, which reclaims only a lock whose recorded owner is provably gone |
|
|
20791
|
+
| `DiagnosticJournalStartupScanSchema` / `DiagnosticJournalStartupScan` | schema / _type_ | how much of the retained generations opening a journal reads: `tails` (default) checks only the end of each file for a torn line, `full` parses every line |
|
|
20179
20792
|
| `readDiagnosticJournalLockDiagnosis` / `DiagnosticJournalLockDiagnosis` | function / _type_ | why a `reclaim-stale` acquisition refused — attribution, PID liveness and optional process `identity` evidence (`matched`, `different-boot`, `reused-pid`, `pid-gone`, `legacy`, `unavailable`) — read off the thrown `EEXIST` |
|
|
20180
20793
|
| `DiagnosticJournalSubmitResultSchema` / `DiagnosticJournalSubmitResult` | schema / _type_ | accepted epoch/sequence or explicit invalid, oversized, capacity, closed or failed refusal |
|
|
20181
20794
|
| `DiagnosticJournalStatusSchema` / `DiagnosticJournalStatus` | schema / _type_ | state, limits, exact admission/write/failure counters, pending ownership, rotations, partial tails and last safe sequences |
|
|
@@ -20188,8 +20801,10 @@ cutovers are covered by the executable
|
|
|
20188
20801
|
| `DiagnosticJournalWaitOptions` / `DiagnosticJournalFailure` | _type_ | caller wait signal/timeout and isolated internal failure callback record |
|
|
20189
20802
|
| `DiagnosticJournalAnomalySchema` / `DiagnosticJournalAnomaly` | schema / _type_ | corrupt-row reason, file, zero-based byte offset, one-based line, tail/interior position and skipped bytes |
|
|
20190
20803
|
| `createDiagnosticJournalReadResultSchema` / `DiagnosticJournalReadResult` | function / _type_ | owner-schema frame output or explicit anomaly; retains transformed event output types |
|
|
20191
|
-
| `DiagnosticJournalRecoveryStatusSchema` / `DiagnosticJournalRecoveryStatus` | schema / _type_ | bounded startup inspection counts
|
|
20192
|
-
| `
|
|
20804
|
+
| `DiagnosticJournalRecoveryStatusSchema` / `DiagnosticJournalRecoveryStatus` | schema / _type_ | bounded startup inspection counts, first/last anomaly locations and every quarantined file beside the journal (`quarantined`, this open's moves first with their `reason`, at most 32; `quarantinedUnlisted` counts the rest); evolving and `.strict()`, so every shape change is a breaking changelog item |
|
|
20805
|
+
| `DiagnosticJournalStartupRefusalPolicySchema` / `DiagnosticJournalStartupRefusalPolicy` | schema / _type_ | `quarantine` (default) renames a file opening cannot keep in place to `<file>.quarantined-<epoch>` and starts; `fail` throws and moves nothing |
|
|
20806
|
+
| `DiagnosticJournalStartupRefusalReasonSchema` / `DiagnosticJournalStartupRefusalReason` | schema / _type_ | `torn-without-retention-slot \| not-a-regular-file \| unreadable` |
|
|
20807
|
+
| `DiagnosticJournalRecoveryError` / `DiagnosticJournalRecoveryErrorOptions` | class / _type_ | typed startup refusal (`reason`, `file`, optional `recovery`, `quarantineFailed`) thrown under `fail` or when moving a file aside failed; preserves evidence and releases its lock |
|
|
20193
20808
|
|
|
20194
20809
|
`accepted` is bounded in-memory admission and `written` is completed append, not `fsync` or durable
|
|
20195
20810
|
delivery. Its filesystem reader is in the diagnostic-journal leaf; there is no managed replay/upload API. See the
|
|
@@ -20495,7 +21110,7 @@ Server-only optional application runtime. See the
|
|
|
20495
21110
|
|
|
20496
21111
|
| Export | Kind | Summary |
|
|
20497
21112
|
|--------|------|---------|
|
|
20498
|
-
| `AgentConversationArchiveSchema` / `AgentConversationArchive` / `encodeAgentConversationArchive` / `decodeAgentConversationArchive` / `canonicalAgentJson` | schema / _type_ / functions | canonical validated conversation archive with exact ledger events, recovery projection and durable spill payloads |
|
|
21113
|
+
| `AgentConversationArchiveSchema` / `AgentConversationArchive` / `encodeAgentConversationArchive` / `decodeAgentConversationArchive` / `canonicalAgentJson` | schema / _type_ / functions | canonical validated conversation archive (`canonicalAgentJson` is `canonicalJson` with the node and byte limits at their maximum, reading `-0` as `0` like `z.json()`) with exact ledger events, recovery projection and durable spill payloads |
|
|
20499
21114
|
| `AgentStoreEventEnvelopeSchema` / `AgentStoreEventEnvelope` / `AgentStoreEventKindSchema` / `AgentStoreEventKind` / `AgentStoreEventPageSchema` / `AgentStoreEventPage` | schema / _type_ | append-only event envelope, closed current vocabulary (including `provider/request` and `provider/response`) and bounded page |
|
|
20500
21115
|
| `AppendAgentStoreEventSchema` / `AppendAgentStoreEvent` / `ReadAgentStoreEventsSchema` / `ReadAgentStoreEvents` | schema / _type_ | required append and bounded-read store contracts |
|
|
20501
21116
|
| `AgentStoreTransitionSchema` / `AgentStoreTransition` / `AgentStoreEventDraft` / `AgentStoreEventDecodeAccepted` / `AgentStoreEventDecodeIgnored` / `AgentStoreEventDecodeResult` / `decodeAgentStoreEvent` | schema / _types_ / function | normalized runtime mutations, append drafts and strict current/ignorable future-event decoding outcomes |
|
|
@@ -20505,7 +21120,7 @@ Server-only optional application runtime. See the
|
|
|
20505
21120
|
| `defineStateSlot` / `AgentStateSlotDefinition` / `AgentStateSlotStore` / `AgentStateSlotValue` / `AnyAgentStateSlot` / `createAgentStateSlotStore` / `renderAgentStateSlots` | functions / _types_ | typed durable state written as ledger events and injected on every provider request |
|
|
20506
21121
|
| `agentGoalStateSlot` / `agentTodoStateSlot` / `createAgentStateTools` | constants / function | built-in goal/todo state and bound `goal_*` / `todo_write` Agent tools |
|
|
20507
21122
|
| `AgentSandboxGradeSchema` / `AgentSandboxGrade` / `AgentSandboxRestrictionSchema` / `AgentSandboxRestriction` / `AgentProcessSandbox` | schema / _type_ | host-provided process sandbox capability and explicit restriction vocabulary |
|
|
20508
|
-
| `probeAgentProcessSandbox` / `missingSandboxRestrictions` / `recordAgentSandboxProbe` | functions |
|
|
21123
|
+
| `probeAgentProcessSandbox` / `missingSandboxRestrictions` / `recordAgentSandboxProbe` | functions | probe cached per sandbox object (keep the sandbox long-lived), fail-closed required-gap calculation and durable probe record; a probe that throws or returns no valid grade resolves to `unavailable` with a fixed reason, its cause emitted as a process warning (the coding tools instead carry it as the refusal's `cause` to the mount's `onToolError` hook) |
|
|
20509
21124
|
| `AgentSandboxProcess` / `AgentSandboxOutputStream` | _types_ | structural child/output interface for optional lifecycle-owned `AgentProcessSandbox.spawn`, without Node ambient type dependencies |
|
|
20510
21125
|
| `AgentEventSearchResultSchema` / `AgentEventSearchResult` / `createSqliteAgentEventSearch` / `createAgentEventSearchTools` | schema / _type_ / functions | authorized FTS5 search with exact event addresses and `session_*` tools |
|
|
20511
21126
|
| `createSqliteAgentSpillStore` | function | durable content-address-checked artifact storage, bounded read/search, retention facts and archive participation |
|
|
@@ -20800,7 +21415,10 @@ terminal and provider-envelope schemas/types listed under
|
|
|
20800
21415
|
including `AgentAttemptResetEventSchema` for `attempt-reset`),
|
|
20801
21416
|
`AgentRuntimeEventCursorSchema`, `advanceAgentRuntimeEventCursor`,
|
|
20802
21417
|
`AgentControlRequestSchema` / `AgentControlRequest`, `AgentControlResponseSchema` /
|
|
20803
|
-
`AgentControlResponse`, `AgentControlDeliverySchema` / `AgentControlDelivery`,
|
|
21418
|
+
`AgentControlResponse`, `AgentControlDeliverySchema` / `AgentControlDelivery`,
|
|
21419
|
+
`AgentControlErrorCodeSchema` / `AgentControlErrorCode` (the one vocabulary of every control
|
|
21420
|
+
error code: `FORBIDDEN`, `CONNECTION_CLOSED`, `REQUEST_CAPACITY`, `ATTACHMENT_CAPACITY`,
|
|
21421
|
+
`LEASE_CONFLICT`, `EVENT_OVERFLOW` and the rest), `AgentMultiSessionCursorSchema` /
|
|
20804
21422
|
`AgentMultiSessionCursor`, `AgentConversationView`, `AgentControlView`,
|
|
20805
21423
|
`advanceAgentMultiSessionCursor`, `createAgentControlView`, `reduceAgentControlSnapshot`,
|
|
20806
21424
|
`reduceAgentControlEvent` and
|
|
@@ -20830,9 +21448,12 @@ attachment per conversation on that transport; share the controller between view
|
|
|
20830
21448
|
`submit` requires a caller-owned idempotency key. Unknown-outcome mutations are never
|
|
20831
21449
|
replayed automatically. `AgentControllerState` distinguishes connecting, ready,
|
|
20832
21450
|
disconnected, error and closed and retains the canonical full `view` plus safe error
|
|
20833
|
-
code/message. Errors in automatic attach/refresh appear there
|
|
20834
|
-
|
|
20835
|
-
|
|
21451
|
+
code/message. Errors in automatic attach/refresh appear there, and so does an
|
|
21452
|
+
`access-denied` delivery: the server detached the conversation, the state becomes
|
|
21453
|
+
`error` with code `FORBIDDEN`, and nothing arrives until the controller attaches again.
|
|
21454
|
+
Reconnect attaches again and reads a snapshot; requests of the previous connection are
|
|
21455
|
+
rejected at once and release their capacity, snapshot gaps coalesce, stale generations
|
|
21456
|
+
cannot overwrite state, and buffers/queues are bounded. Overflow or failed refresh is visible and requires
|
|
20836
21457
|
explicit recovery. Closing detaches without closing the shared transport or remote run.
|
|
20837
21458
|
|
|
20838
21459
|
See [agent composition](../guide/agent-composition.md) for a complete wiring example.
|
|
@@ -21061,10 +21682,10 @@ payload.
|
|
|
21061
21682
|
| `McpMountConfig` | _type_ | config for `mountMcp` |
|
|
21062
21683
|
| `McpSchemaValidationConfig` | _type_ | shared `{ policy, requireTypedProperties, allowUntyped, requirePortableFormats, allowFormats }` profile |
|
|
21063
21684
|
| `ValidateMcpSchemasConfig` | _type_ | standalone validation profile plus `services`, `extend`, flattening and logger |
|
|
21064
|
-
| `RuntimeToolDefinition` | _type_ |
|
|
21685
|
+
| `RuntimeToolDefinition` | _type_ | heterogeneous registration of pathless operations in `runtimeTools: [...]`; an inline handler is contextually typed by the parsed object, a definition constructed with its own schema registers unchanged, and the runner validates input before the handler |
|
|
21065
21686
|
| `RuntimeToolDefinitionBase` | _type_ | common name, identity, input, exposure and MCP metadata fields |
|
|
21066
|
-
| `RuntimeToolDefinitionWithOutput` | _type_ |
|
|
21067
|
-
| `RuntimeToolDefinitionWithoutOutput` | _type_ |
|
|
21687
|
+
| `RuntimeToolDefinitionWithOutput` | _type_ | the one neutral construction: identity, schemas and a handler typed from them; the fourth type argument names the adapter's `present` extension (`RuntimeToolPresenters<…>` here, `RuntimeMcpToolPresenters<…>` on `stitchkit/tools/mcp`, none by default) |
|
|
21688
|
+
| `RuntimeToolDefinitionWithoutOutput` | _type_ | the same construction with a void handler and no presentation callbacks |
|
|
21068
21689
|
| `RuntimeToolFactory` | _type_ | identity/context-bound runtime-tool definition factory |
|
|
21069
21690
|
| `RuntimeToolFactoryConfig` | _type_ | factory service identity and context schema |
|
|
21070
21691
|
| `RuntimeToolFactoryDefinitionWithOutput` | _type_ | factory-authored runtime tool with a validated output schema |
|
|
@@ -21273,10 +21894,9 @@ Advanced building blocks — the shared machinery the mounts are built on.
|
|
|
21273
21894
|
| `ToolCallRecord` | _type_ | the structured record `createToolLogger` passes to `onRecord` |
|
|
21274
21895
|
| `TransportSummary` | _type_ | `{ contractServices, runtimeTools, totals, sources }` from `summarizeTransports` |
|
|
21275
21896
|
| `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
|
|
21276
|
-
| `
|
|
21277
|
-
| `ToolSurfaceProjection` | _type_ | one readonly services/runtime container shared by full and MCP-only surfaces; executable mounts specialize the runtime definition without pulling in another adapter's peers |
|
|
21897
|
+
| `ToolSurfaceProjection` | _type_ | the one readonly `{ services?, runtimeTools? }` container shared by full, MCP-only and introspection surfaces (`buildToolManifest`, `listToolNames`, `summarizeTransports` take `ToolSurfaceProjection<RuntimeToolDefinition>`) |
|
|
21278
21898
|
| `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
|
|
21279
|
-
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
|
|
21899
|
+
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments; throws a `ZodError` naming the field when a union that keeps a string member receives a string that parses to an array its array member accepts |
|
|
21280
21900
|
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions into conservative object joins; scalar collisions retain provable types, object/array collisions retain structural alternatives, and field descriptions retain discriminator labels plus availability/requiredness hints; the projection never executes validation |
|
|
21281
21901
|
| `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
|
|
21282
21902
|
| `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
|
|
@@ -21295,10 +21915,9 @@ behavior and names. See [MCP guide](../guide/mcp-and-agents.md#mcp--createmcphan
|
|
|
21295
21915
|
|--------|------|---------|
|
|
21296
21916
|
| `createMcpHandler` / `createMcpHttpRoute` / `buildMcpServer` / `mountMcp` | function | the shared authenticated HTTP handler, raw-route adapter, SDK server and contract mount |
|
|
21297
21917
|
| `createStdioMcpServer` / `bindStdioProcessSignals` | function | the shared stdio server and bounded process cleanup |
|
|
21298
|
-
| `
|
|
21299
|
-
| `
|
|
21918
|
+
| `RuntimeToolDefinitionWithOutput` / `RuntimeToolDefinitionWithoutOutput` | _type_ | the one neutral construction, shared with `stitchkit/cli` and `stitchkit/tools`; MCP-only presenters are its fourth type argument, `RuntimeMcpToolPresenters<z.output<typeof output>>` |
|
|
21919
|
+
| `RuntimeToolDefinition` | _type_ | heterogeneous registration; the canonical runner validates input/output before invocation |
|
|
21300
21920
|
| `RuntimeMcpToolPresenters` / `RuntimeMcpPresentation` | _type_ | official SDK content without framework-owned `structuredContent` or `isError`; one presenter owner shared with the full SDK surface |
|
|
21301
|
-
| `RuntimeToolDefinitionWithoutOutput` | _type_ | the shared neutral void definition |
|
|
21302
21921
|
|
|
21303
21922
|
## `stitchkit/tools/connections`
|
|
21304
21923
|
|
|
@@ -21398,7 +22017,6 @@ handler pipeline without opening a TCP port.
|
|
|
21398
22017
|
| `McpSchemaValidationConfig` / `IncompatibleSchemaPolicy` | _type_ | canonical MCP schema-preparation policy used by real mounts and manifests |
|
|
21399
22018
|
| `SurfaceRealtimeSchemaPairSchema` | schema | input/output digest pair for args or acknowledgements |
|
|
21400
22019
|
| `SurfaceSchemaDigestsSchema` | schema | params/input/output/multipart digest object |
|
|
21401
|
-
| `serializeSurfaceValue` | function | canonical versioned serialization used for deterministic digests |
|
|
21402
22020
|
|
|
21403
22021
|
---
|
|
21404
22022
|
|
|
@@ -21419,8 +22037,10 @@ available from `stitchkit/contract`.
|
|
|
21419
22037
|
| `ManagedFileError` / `ManagedFileErrorCode` | class / _type_ | stable boundary failures; registered `FILE_*` mistakes are caller-safe while `FILE_IO_ERROR` remains internal |
|
|
21420
22038
|
| `ManagedFileInspector` | _type_ | bounded-prefix read/write inspection callback with a finite cancellation signal that cannot own path or size |
|
|
21421
22039
|
| `ManagedFileInspectionInput` / `ManagedFileInspection` | _type_ | inspector prefix/name/declared media/signal input and validated metadata-only result |
|
|
21422
|
-
| `writeFileAtomic` / `writeFileAtomicSync` / `WriteFileAtomicOptions` | function / _type_ | replace a file atomically: a random staging name created exclusively (never through a planted link), the mode set on the descriptor before the file is visible (default `0o600`, not masked by the umask), `fsync`, rename; a failure leaves the target and no staging file. The asynchronous form keeps the event loop running |
|
|
21423
|
-
| `
|
|
22040
|
+
| `writeFileAtomic` / `writeFileAtomicSync` / `WriteFileAtomicOptions` | function / _type_ | replace a file atomically: a random staging name created exclusively (never through a planted link), the mode set on the descriptor before the file is visible (default `0o600`, not masked by the umask), `fsync`, rename; a failure leaves the target and no staging file. The asynchronous form keeps the event loop running. A process killed before publication leaves its staging file; sweeping it is the caller's job |
|
|
22041
|
+
| `isAtomicStagingName` | function | true for a staging file name of an atomic write of this library (`.stitchkit-<24 lowercase hex>.tmp`, also used by the managed writer and the chunk spool); the form is a stable contract, and changing it is a breaking change ([ADR 0242](../decisions/0242-atomic-staging-name-is-a-public-contract.md)) |
|
|
22042
|
+
| `sweepAtomicStaging` / `SweepAtomicStagingOptions` | function / _type_ | remove abandoned staging files from one `directory`: only regular files matching `isAtomicStagingName` and older than the required `olderThanMs`, never following a symlink or entering a subdirectory; stops on `signal`; returns the removed names |
|
|
22043
|
+
| `withExclusiveLock` / `ExclusiveLockOptions` / `ExclusiveLock` / `ExclusiveLockOwner` | function / _type_ | run work under an exclusive lock between processes — a file recording its owner (pid, host, machine identity, time and nullable kernel process identity); waits up to `timeoutMs` (default 10 s) and stops on `signal`; a dead owner on this machine is taken over, a live, slow or foreign one never is; the lock is published with its owner already recorded (temporary file plus hard link), so a stalled holder is never displaced; a legacy empty lock file is taken by age only when `ownerlessGraceMs` is a number (default `null`: never; an abandoned empty reclaim guard is taken after 5 000 ms unless a value is set); exact `mode` is independent of umask; shared readers need a common group and traversable directories |
|
|
21424
22044
|
| `ExclusiveLockError` | class | `LOCK_TIMEOUT` naming the resource and its holder, or `LOCK_ABORTED` with the signal's reason |
|
|
21425
22045
|
| `createChunkSpool` | function | the server half of a chunked upload: parts on disk under `owner` + client-minted `uploadId`, each with a size+sha256 receipt; idempotent `open` and `put`, the first writer of a part wins across processes, `assemble` in order once every part is in, `sweep` of untouched uploads — [guide](../guide/client.md#chunked-uploads) |
|
|
21426
22046
|
| `ChunkSpool` / `ChunkSpoolConfig` / `ChunkSpoolKey` / `ChunkSpoolOpen` / `ChunkSpoolPart` / `ChunkSpoolAssembly` | _type_ | the spool, its directory, part size, file limit, meta schema and age; an upload's key; its declaration; one part; the assembled parts with a concatenating `stream()` |
|
|
@@ -21798,7 +22418,6 @@ SDK nor the `ai` peer.
|
|
|
21798
22418
|
| `checkCliUpdate` | function | bounded, interval-limited, never-throwing check for a newer published build |
|
|
21799
22419
|
| `applyCliUpdate` | function | download, verify the decompressed digest and replace the binary by rename |
|
|
21800
22420
|
| `assertCliPublishable` | function | refuse republishing one version from a different commit — takes one manifest or every manifest already published, because the promise a version makes does not stop at a channel boundary |
|
|
21801
|
-
| `publishCli` / `CliPublicationOptions` / `CliPublicationPhase` / `CliPublicationResult` | function / _type_ | optional bounded builder/admission over one immutable name/version/commit stamp; commit verified assets before the manifest pointer, recover exact completed versions, retain verified history; existing signatures/installer/updater own the wire contract |
|
|
21802
22421
|
| `rollbackCliUpdate` / `CliRollbackConfig` / `RolledBackCliUpdate` | function / _types_ | put the kept previous build back, digest-checked first and written by the same atomic rename the update uses |
|
|
21803
22422
|
| `signCliManifest` / `verifyCliManifest` / `cliManifestSigningPayload` | function | Ed25519 over `{name, version, commit, builtAt, assets[]}` — every asset's digest included, so the chain closes on the file that executes |
|
|
21804
22423
|
| `cliSignatureAccepted` / `CliSignatureVerdict` | function / _type_ | `valid` \| `unenforced` \| `missing` \| `unknown-key` \| `invalid`; `unenforced` keeps an unpinned build updating while making the absence of a check visible |
|
|
@@ -21830,8 +22449,8 @@ SDK nor the `ai` peer.
|
|
|
21830
22449
|
| `CliConfig` | _type_ | config for `createCli`; `defaultCommand`, `globalOptions`, `optionAliases` and `positionals` define the shared command presentation policy |
|
|
21831
22450
|
| `CliPresentationPolicyConfig` | _type_ | shared command presentation-policy subset of `CliConfig` |
|
|
21832
22451
|
| `CliSurfaceSource` | _type_ | static service/runtime array or identity-dependent factory |
|
|
21833
|
-
| `
|
|
21834
|
-
| `
|
|
22452
|
+
| `RuntimeToolDefinition` | _type_ | neutral heterogeneous runtime-tool registration; an inline handler is contextually typed, and handlers execute through the schema-validating managed runner |
|
|
22453
|
+
| `RuntimeToolDefinitionWithOutput` | _type_ | strict neutral managed definition with schema-derived handler input/output, the same declaration `stitchkit/tools` constructs with presenters |
|
|
21835
22454
|
| `RuntimeToolDefinitionWithoutOutput` | _type_ | strict neutral managed void definition without an output contract or transport presenters |
|
|
21836
22455
|
| `CliCommandDefinition` | _type_ | native command definition union |
|
|
21837
22456
|
| `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
|
|
@@ -21848,6 +22467,22 @@ SDK nor the `ai` peer.
|
|
|
21848
22467
|
|
|
21849
22468
|
---
|
|
21850
22469
|
|
|
22470
|
+
## `stitchkit/cli/publish`
|
|
22471
|
+
|
|
22472
|
+
Build and release tooling, evolving. Publishes a CLI distribution into a local
|
|
22473
|
+
application-owned directory; the manifest, signatures, installer and updater it
|
|
22474
|
+
writes for stay in `stitchkit/cli` — [guide](../guide/cli.md#publishing-a-complete-version),
|
|
22475
|
+
[ADR 0245](../decisions/0245-cli-publication-is-an-evolving-leaf.md).
|
|
22476
|
+
|
|
22477
|
+
| Export | Kind | Summary |
|
|
22478
|
+
|--------|------|---------|
|
|
22479
|
+
| `publishCli` | function | optional bounded builder/admission over one immutable name/version/commit stamp; commit verified assets before the manifest pointer, recover exact completed versions, retain verified history |
|
|
22480
|
+
| `CliPublicationOptions` | _type_ | identity, storage root, base URL, targets, limits, retention, the `admit` and `build` callbacks, optional signing and trust |
|
|
22481
|
+
| `CliPublicationPhase` | _type_ | the step `admit` is asked about — `prepare`, `build`, `commit` or `promote` |
|
|
22482
|
+
| `CliPublicationResult` | _type_ | `published` or `existing`, with the manifest ready to serve |
|
|
22483
|
+
|
|
22484
|
+
---
|
|
22485
|
+
|
|
21851
22486
|
## `stitchkit/react`
|
|
21852
22487
|
|
|
21853
22488
|
Browser- and server-render-safe React data-layer helpers. Needs the
|
|
@@ -21936,11 +22571,20 @@ network enforcement, gateway credentials and platform/resource boundaries.
|
|
|
21936
22571
|
|
|
21937
22572
|
## `stitchkit/agent-runtime/realtime`
|
|
21938
22573
|
|
|
21939
|
-
Optional evolving Socket.IO server binding
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
|
|
22574
|
+
Optional evolving Socket.IO server binding, experimental: no committed consumer yet.
|
|
22575
|
+
`bindAgentHarnessRealtime` takes `(harness, socket, config)` and returns an
|
|
22576
|
+
`AgentHarnessRealtimeBinding`: `close()` for its subscriptions and control connections, without closing
|
|
22577
|
+
socket or harness, and `revoke(conversationId, matches?)`. `AgentHarnessRealtimeConfig` requires
|
|
22578
|
+
`authorize({ identity, request, source, signal })`: return `{ context }` after verifying the session
|
|
22579
|
+
principal, conversation, operation, files and metadata, or `null`. Identity is `socket.data` from the
|
|
22580
|
+
application's verified handshake. Every request is authorized on arrival. The events of an
|
|
22581
|
+
attached conversation are delivered under the grant the latest successful request created, so a
|
|
22582
|
+
token stream costs no authorization call per event. `revoke` drops the grant of every matching
|
|
22583
|
+
socket; the next event is re-authorized (`source: 'delivery'`, an `operation: 'snapshot'` request),
|
|
22584
|
+
and a refusal or failure detaches the conversation and sends an `access-denied` delivery, which
|
|
22585
|
+
turns the browser controller into an `error` state. Without a `revoke` call a grant lasts until the socket disconnects or the conversation
|
|
22586
|
+
is detached; this fail-open default is deliberate, so call `revoke` whenever access to a conversation may
|
|
22587
|
+
have been withdrawn. An authorization that began before a `revoke` never restores the grant. Observe access does
|
|
21944
22588
|
not grant mutation; controller leases and approval policy still belong to the harness.
|
|
21945
22589
|
|
|
21946
22590
|
`maxPendingEvents` and `maxPendingAttachments` forward to the canonical control server;
|
|
@@ -21952,23 +22596,11 @@ application transport. A transport's Engine.IO payload limit remains application
|
|
|
21952
22596
|
|
|
21953
22597
|
## `stitchkit/agent-runtime/react`
|
|
21954
22598
|
|
|
21955
|
-
Optional evolving React peer leaf. `useAgent` takes a controller and subscribes through
|
|
22599
|
+
Optional evolving React peer leaf, experimental: no committed consumer yet. `useAgent` takes a controller and subscribes through
|
|
21956
22600
|
`useSyncExternalStore`, returning the same `AgentControllerState` as `getSnapshot()`.
|
|
21957
22601
|
Create and close the controller at application/session scope. Multiple components can
|
|
21958
22602
|
observe it; StrictMode and unmount release only the component's own subscription.
|
|
21959
22603
|
|
|
21960
|
-
## `stitchkit/agent-runtime/harness-tools`
|
|
21961
|
-
|
|
21962
|
-
Optional evolving composition of the full `mountAgent` configuration with the runtime
|
|
21963
|
-
fence. Its runtime peer is `ai`; its full tool presenter declarations also require
|
|
21964
|
-
`@modelcontextprotocol/server`, as `stitchkit/tools` does. The base harness retains its
|
|
21965
|
-
AI-only peer boundary. Existing custom tool callbacks need no new import.
|
|
21966
|
-
|
|
21967
|
-
| Export | Kind | Summary |
|
|
21968
|
-
|--------|------|---------|
|
|
21969
|
-
| `createAgentHarnessTools` / `AgentHarnessToolsConfig` | function / _type_ | async per-run full mount configuration (`services` plus all `AgentMountConfig` options); composes application lifecycle then runtime fence exactly once |
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
22604
|
## `stitchkit/process`
|
|
21973
22605
|
|
|
21974
22606
|
Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/native-io.md).
|
|
@@ -21976,7 +22608,8 @@ Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/nativ
|
|
|
21976
22608
|
| Export | Kind | Purpose |
|
|
21977
22609
|
|---|---|---|
|
|
21978
22610
|
| `runNativeCommand` | function | caller-lifetime streaming or explicitly bounded binary capture |
|
|
21979
|
-
| `NativeCommandOptions` | type | executable/args, env policy, signal/deadline, bytes sink
|
|
22611
|
+
| `NativeCommandOptions` | type | executable/args, env policy, signal/deadline, bytes sink, finite cleanup limits, the `stop` policy and `descendants` (`'terminate-after-leader'`, the default: stop the group after the leader exits, or `'leave'`) |
|
|
22612
|
+
| `NativeCommandStopPolicy` | type | how a cancelled command stops: `target` `'group'` (signal every member) or `'leader'` (signal only the leader; its exit ends the grace), `signal` (default `SIGTERM`), `graceMs` (at most one hour) before KILL to the group, and `killOn`, an `AbortSignal` that skips the grace; default `{ target: 'group', signal: 'SIGTERM', graceMs: 100 }` |
|
|
21980
22613
|
| `NativeCommandResult` | type | observed nullable exit code/signal and captured stdout/stderr bytes |
|
|
21981
22614
|
| `NativeCommandError` | class | COMMAND_LIMIT with machine reason `deadline` or `output-budget`; COMMAND_UNAVAILABLE / COMMAND_CLEANUP have no limit reason. Preserves internal cause and existing constructor; caller-created limits without supplied reason remain unclassified |
|
|
21982
22615
|
|
|
@@ -21989,12 +22622,27 @@ Native POSIX Bun/Node one-shot command execution. See [native IO](../guide/nativ
|
|
|
21989
22622
|
|
|
21990
22623
|
`stitchkit/files/packaging` owns `createNativePackaging` and the types
|
|
21991
22624
|
`NativePackagingOptions`, `NativePackagingAsset`, `NativePackagingPlugin`, `NativePackagingResult`.
|
|
21992
|
-
It resolves the installed package's target/version
|
|
21993
|
-
|
|
21994
|
-
|
|
21995
|
-
|
|
21996
|
-
|
|
21997
|
-
|
|
21998
|
-
|
|
22625
|
+
It resolves the installed package's target/version, reads each selected addon once and checks it
|
|
22626
|
+
against the `size` and `sha256` published in the package's `native-assets.json` (`formatVersion: 2`,
|
|
22627
|
+
written when Stitchkit is built). Call it once per build and reuse the result.
|
|
22628
|
+
`platform`, `architecture`, `delivery`, `entryPath` and `assetPath` are explicit public inputs;
|
|
22629
|
+
`platform` is the closed set `'darwin'`, and another name is a type error and a schema refusal.
|
|
22630
|
+
The result is `ready` with verified assets and a structural Bun plugin, or `unsupported` /
|
|
22631
|
+
`missing` / `mismatch` with the safe code `NATIVE_TARGET_UNSUPPORTED`, `NATIVE_ASSET_MISSING` or
|
|
22632
|
+
`NATIVE_ASSET_DIGEST_MISMATCH`. A `mismatch` names the first differing `architecture` with
|
|
22633
|
+
`expected` (published) and `actual` (installed) `{ size, sha256 }`.
|
|
22634
|
+
Malformed options, a manifest of another `formatVersion` and other filesystem errors throw rather
|
|
22635
|
+
than masquerade as missing.
|
|
22636
|
+
Companion accepts a single `architecture` string with an `assetPath` string, or an architecture
|
|
22637
|
+
array with an exact per-architecture `assetPath` map. `NativePackagingOptions<true>` and
|
|
22638
|
+
`NativePackagingResult<true>` describe the array form; default types preserve single-target signatures.
|
|
22639
|
+
A ready array-form result carries the architecture array; `NativePackagingAsset<true>` identifies
|
|
22640
|
+
each asset architecture. A single-target asset has four fields: `outputPath`, the verified `bytes`,
|
|
22641
|
+
and the published `size` and `sha256`.
|
|
22642
|
+
Targets must be unique; entry and all asset paths must be disjoint. One lazy loader chooses only
|
|
22643
|
+
the actual runtime architecture, with no fallback.
|
|
22644
|
+
Companion mode places external addons relative to the emitted entry; embedded mode gives
|
|
22645
|
+
Bun compile the verified bytes of the matching addon. Keep JS builds without splitting, preserve
|
|
22646
|
+
every output, and write each asset's `bytes` to its `outputPath`.
|
|
21999
22647
|
This evolving entrypoint is a build dependency; runtime imports do not reach it.
|
|
22000
22648
|
The detailed recipe is in [native IO](../guide/native-io.md#public-native-packaging).
|