stitchkit 0.110.5 → 0.110.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/agent-runtime/branch-observations.d.ts +16 -16
  3. package/dist/entrypoints/agent-runtime/coding-tools.js +2 -2
  4. package/dist/entrypoints/agent-runtime/sandbox.js +2 -2
  5. package/dist/entrypoints/agent-runtime/sqlite/bun.js +4 -4
  6. package/dist/entrypoints/agent-runtime/sqlite/node.js +4 -4
  7. package/dist/entrypoints/agent-runtime/testing.js +4 -4
  8. package/dist/entrypoints/agent-runtime.js +4 -4
  9. package/dist/entrypoints/process.js +1 -1
  10. package/dist/entrypoints/server.d.ts +1 -0
  11. package/dist/entrypoints/server.d.ts.map +1 -1
  12. package/dist/entrypoints/server.js +55 -3
  13. package/dist/{index-mpg1t7nv.js → index-0z3kfphz.js} +1 -1
  14. package/dist/{index-5pbtbce0.js → index-7c957abz.js} +1 -1
  15. package/dist/{index-e1sjcjfy.js → index-7r4knwf8.js} +94 -23
  16. package/dist/{index-bm180f2d.js → index-ey15wx6j.js} +1 -1
  17. package/dist/{index-9ket8wzx.js → index-mnk5vgdr.js} +1 -1
  18. package/dist/process/group-diagnostics.d.ts +20 -0
  19. package/dist/process/group-diagnostics.d.ts.map +1 -0
  20. package/dist/process/group-settlement.d.ts +1 -1
  21. package/dist/process/group-settlement.d.ts.map +1 -1
  22. package/dist/process/group.d.ts.map +1 -1
  23. package/dist/server/bun.d.ts +9 -0
  24. package/dist/server/bun.d.ts.map +1 -1
  25. package/dist/server/proxy-origin.d.ts +12 -0
  26. package/dist/server/proxy-origin.d.ts.map +1 -0
  27. package/llms/server.2.txt +131 -0
  28. package/llms/server.5.txt +1 -0
  29. package/llms/server.txt +55 -131
  30. package/llms-full.txt +56 -0
  31. package/llms.txt +1 -1
  32. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -15,6 +15,17 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.110.6] - 2026-10-15
19
+
20
+ ### Added
21
+
22
+ - `stitchkit/server`: `createServer({ proxyOrigin: { trustedPeer } })` restores the external HTTP/HTTPS scheme from a single trusted ingress before fetch composition and routing. It validates the real TCP peer and one `X-Forwarded-Proto`, rejects competing URL headers, preserves wire Host/ports and native cancellation, peer lookup and WebSocket upgrade. `trustProxy` remains client-IP-only. The server surface grows by one configuration type, `ProxyOriginConfig`, owned by this entrypoint so the trust decision is explicit and independently testable (I8, I9, I12).
23
+
24
+ ### Fixed
25
+
26
+ - `stitchkit/process`: bounded process-group settlement observations remain readable through nested cleanup errors in Node CI, including up to 16 actual states, the group id, sample count and elapsed time. Observation failures retain their original cause. Cleanup deadlines and the definition of a live process are unchanged; these diagnostics do not claim to repair a historical native termination failure.
27
+ - `stitchkit/process`: stopping a process group repeats `SIGKILL` for members still confirmed alive within the original cleanup deadline. Fresh kernel absence ends further signals; dead-only, invalid and failed process observations never trigger a blind re-signal.
28
+
18
29
  ## [0.110.5] - 2026-10-15
19
30
 
20
31
  ### Added
@@ -25,20 +25,8 @@ export declare function branchObservation(ids: BranchIdentities, kind: string, r
25
25
  delayMs?: number | undefined;
26
26
  reason?: string | undefined;
27
27
  } | {
28
- effectName: string;
29
- phase: "intent";
30
- runId: string;
31
- id?: string | undefined;
32
- } | {
33
- effectName: string;
34
- phase: "accepted";
28
+ stepName: string;
35
29
  encoded: string;
36
- via: "reconcile" | "run";
37
- runId: string;
38
- id?: string | undefined;
39
- } | {
40
- effectName: string;
41
- phase: "uncertain";
42
30
  runId: string;
43
31
  id?: string | undefined;
44
32
  } | {
@@ -53,15 +41,27 @@ export declare function branchObservation(ids: BranchIdentities, kind: string, r
53
41
  id: string;
54
42
  runId: string;
55
43
  } | {
56
- stepName: string;
44
+ event: string;
45
+ id: string;
57
46
  encoded: string;
58
47
  runId: string;
48
+ } | {
49
+ effectName: string;
50
+ phase: "intent";
51
+ runId: string;
59
52
  id?: string | undefined;
60
53
  } | {
61
- event: string;
62
- id: string;
54
+ effectName: string;
55
+ phase: "accepted";
63
56
  encoded: string;
57
+ via: "reconcile" | "run";
58
+ runId: string;
59
+ id?: string | undefined;
60
+ } | {
61
+ effectName: string;
62
+ phase: "uncertain";
64
63
  runId: string;
64
+ id?: string | undefined;
65
65
  } | {
66
66
  bytes: number;
67
67
  sha256?: string | undefined;
@@ -6,10 +6,10 @@ import {
6
6
  AgentCodingToolAuthorizationSchema2,
7
7
  AgentCodingToolPathAuthorizationSchema2,
8
8
  createAgentCodingTools2
9
- } from "../../index-bm180f2d.js";
9
+ } from "../../index-ey15wx6j.js";
10
10
  import"../../index-2kbysk9s.js";
11
11
  import"../../index-84bf5spd.js";
12
- import"../../index-e1sjcjfy.js";
12
+ import"../../index-7r4knwf8.js";
13
13
  import"../../index-zrtrjkbf.js";
14
14
  import"../../index-636mggad.js";
15
15
  export {
@@ -3,7 +3,7 @@ import {
3
3
  missingSandboxRestrictions2,
4
4
  codingRefusal,
5
5
  createAgentCodingTools2
6
- } from "../../index-bm180f2d.js";
6
+ } from "../../index-ey15wx6j.js";
7
7
  import"../../index-ct75c3at.js";
8
8
  import"../../index-q6crhe1n.js";
9
9
  import"../../index-fynr93w4.js";
@@ -17,7 +17,7 @@ import {
17
17
  nativeCommandOwner,
18
18
  terminateOwnedGroup,
19
19
  startNativeCommand
20
- } from "../../index-e1sjcjfy.js";
20
+ } from "../../index-7r4knwf8.js";
21
21
  import"../../index-zrtrjkbf.js";
22
22
  import {
23
23
  MAX_TIMER_MS
@@ -1,20 +1,20 @@
1
1
  import"../../../index-jna0ejsa.js";
2
- import"../../../index-mpg1t7nv.js";
2
+ import"../../../index-0z3kfphz.js";
3
3
  import"../../../index-8kzct1d6.js";
4
4
  import {
5
5
  initializeAgentRuntimeSqlite2,
6
6
  createSqliteAgentRuntimeStore2
7
- } from "../../../index-5pbtbce0.js";
7
+ } from "../../../index-7c957abz.js";
8
8
  import"../../../index-kqnd6a85.js";
9
9
  import"../../../index-5w6t91ze.js";
10
- import"../../../index-bm180f2d.js";
10
+ import"../../../index-ey15wx6j.js";
11
11
  import"../../../index-ct75c3at.js";
12
12
  import"../../../index-q6crhe1n.js";
13
13
  import"../../../index-fynr93w4.js";
14
14
  import"../../../index-2kbysk9s.js";
15
15
  import"../../../index-84bf5spd.js";
16
+ import"../../../index-7r4knwf8.js";
16
17
  import"../../../index-snef22zr.js";
17
- import"../../../index-e1sjcjfy.js";
18
18
  import"../../../index-zrtrjkbf.js";
19
19
  import"../../../index-636mggad.js";
20
20
 
@@ -1,20 +1,20 @@
1
1
  import"../../../index-jna0ejsa.js";
2
- import"../../../index-mpg1t7nv.js";
2
+ import"../../../index-0z3kfphz.js";
3
3
  import"../../../index-8kzct1d6.js";
4
4
  import {
5
5
  initializeAgentRuntimeSqlite2,
6
6
  createSqliteAgentRuntimeStore2
7
- } from "../../../index-5pbtbce0.js";
7
+ } from "../../../index-7c957abz.js";
8
8
  import"../../../index-kqnd6a85.js";
9
9
  import"../../../index-5w6t91ze.js";
10
- import"../../../index-bm180f2d.js";
10
+ import"../../../index-ey15wx6j.js";
11
11
  import"../../../index-ct75c3at.js";
12
12
  import"../../../index-q6crhe1n.js";
13
13
  import"../../../index-fynr93w4.js";
14
14
  import"../../../index-2kbysk9s.js";
15
15
  import"../../../index-84bf5spd.js";
16
+ import"../../../index-7r4knwf8.js";
16
17
  import"../../../index-snef22zr.js";
17
- import"../../../index-e1sjcjfy.js";
18
18
  import"../../../index-zrtrjkbf.js";
19
19
  import"../../../index-636mggad.js";
20
20
 
@@ -10,18 +10,18 @@ import {
10
10
  advanceToolChronology
11
11
  } from "../../index-jna0ejsa.js";
12
12
  import"../../index-5w6t91ze.js";
13
- import"../../index-bm180f2d.js";
14
- import"../../index-mpg1t7nv.js";
13
+ import"../../index-ey15wx6j.js";
14
+ import"../../index-0z3kfphz.js";
15
15
  import"../../index-ct75c3at.js";
16
16
  import"../../index-q6crhe1n.js";
17
17
  import"../../index-fynr93w4.js";
18
18
  import"../../index-2kbysk9s.js";
19
19
  import"../../index-84bf5spd.js";
20
+ import"../../index-7r4knwf8.js";
20
21
  import"../../index-snef22zr.js";
21
22
  import {
22
23
  createMemoryAgentRuntimeStore2
23
- } from "../../index-9ket8wzx.js";
24
- import"../../index-e1sjcjfy.js";
24
+ } from "../../index-mnk5vgdr.js";
25
25
  import"../../index-zrtrjkbf.js";
26
26
  import {
27
27
  assertPositiveSafeInteger
@@ -5,7 +5,7 @@ import {
5
5
  missingSandboxRestrictions2,
6
6
  recordAgentSandboxProbe2,
7
7
  refuseUnknownSpillReference
8
- } from "../index-bm180f2d.js";
8
+ } from "../index-ey15wx6j.js";
9
9
  import {
10
10
  AgentRecordIdSchema2,
11
11
  AgentRecordVersionSchema2,
@@ -58,7 +58,7 @@ import {
58
58
  AgentHistoryMutationSchema2,
59
59
  ACTIVE_AGENT_RUN_STATES2,
60
60
  createAgentRuntimeStore2
61
- } from "../index-mpg1t7nv.js";
61
+ } from "../index-0z3kfphz.js";
62
62
  import {
63
63
  AgentStoreEventKindSchema2,
64
64
  AgentStoreEventEnvelopeSchema2,
@@ -188,13 +188,13 @@ import {
188
188
  import"../index-56yx8dxp.js";
189
189
  import"../index-2kbysk9s.js";
190
190
  import"../index-84bf5spd.js";
191
+ import"../index-7r4knwf8.js";
191
192
  import {
192
193
  createMemoryAgentRuntimeStore2
193
- } from "../index-9ket8wzx.js";
194
+ } from "../index-mnk5vgdr.js";
194
195
  import {
195
196
  createAgentRuntimeEventSink2
196
197
  } from "../index-616xh7f2.js";
197
- import"../index-e1sjcjfy.js";
198
198
  import"../index-zrtrjkbf.js";
199
199
  import {
200
200
  MAX_TIMER_MS
@@ -9,7 +9,7 @@ import {
9
9
  } from "../index-84bf5spd.js";
10
10
  import {
11
11
  startNativeCommand
12
- } from "../index-e1sjcjfy.js";
12
+ } from "../index-7r4knwf8.js";
13
13
  // src/process/command.ts
14
14
  function runNativeCommand(input) {
15
15
  return startNativeCommand(input).result;
@@ -23,6 +23,7 @@ export { createTrustFence, isLoopbackAddress, type TrustFence, type TrustFenceCo
23
23
  export { type MultipartLifecycle, type MultipartResult, parseMultipart, } from '../server/multipart.js';
24
24
  export { generateOpenApiDocument, type OpenApiConfig, type OpenApiDocument, type OpenApiInfo, type OpenApiServer, openApiRoute, } from '../server/openapi.js';
25
25
  export { bindProcessSignals, type ProcessSignalName, type ProcessSignalsBinding, type ProcessSignalsErrorPhase, type ProcessSignalsOptions, type ShutdownTarget, type SignalSource, } from '../server/process-signals.js';
26
+ export type { ProxyOriginConfig } from '../server/proxy-origin.js';
26
27
  export { createRateLimiter, type RateLimitConfig } from '../server/rate-limit.js';
27
28
  export { errorResponse, parseBody, respondJson } from '../server/raw.js';
28
29
  export { bindRealtimeServer, type RealtimeServer, type RealtimeServerConnection, type RealtimeServerHandle, } from '../server/realtime.js';
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/entrypoints/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/entrypoints/server.ts"],"names":[],"mappings":"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"}
@@ -335,6 +335,53 @@ function bindSocketRegistry(server, options) {
335
335
  }
336
336
  // src/server/bun.ts
337
337
  import { chmodSync, statSync, unlinkSync } from "node:fs";
338
+
339
+ // src/server/proxy-origin.ts
340
+ import { z } from "zod";
341
+ var ProtocolSchema = z.enum(["http", "https"]);
342
+ var AuthoritySchema = z.string().min(1).max(1024).regex(/^[^\s/\\?#@,;]+$/);
343
+ var conflictingHeaders = ["forwarded", "x-forwarded-host", "x-forwarded-port"];
344
+ function createProxyOrigin(policy) {
345
+ const originals = new WeakMap;
346
+ const original = (request) => originals.get(request) ?? request;
347
+ return {
348
+ bind(server) {
349
+ const requestIP = server.requestIP.bind(server);
350
+ const timeout = server.timeout.bind(server);
351
+ const upgrade = server.upgrade.bind(server);
352
+ server.requestIP = (request) => requestIP(original(request));
353
+ server.timeout = (request, seconds) => timeout(original(request), seconds);
354
+ server.upgrade = (request, options) => upgrade(original(request), options);
355
+ },
356
+ normalize(request, server) {
357
+ const peer = server.requestIP(request)?.address;
358
+ if (!peer || !policy.trustedPeer(peer))
359
+ return request;
360
+ const protocol = ProtocolSchema.safeParse(request.headers.get("x-forwarded-proto"));
361
+ const authority = AuthoritySchema.safeParse(request.headers.get("host"));
362
+ if (!protocol.success || !authority.success || conflictingHeaders.some((name) => request.headers.has(name))) {
363
+ return new Response("Invalid proxy origin", { status: 400 });
364
+ }
365
+ let origin;
366
+ try {
367
+ origin = new URL(`${protocol.data}://${authority.data}`);
368
+ } catch {
369
+ return new Response("Invalid proxy origin", { status: 400 });
370
+ }
371
+ const url = new URL(request.url);
372
+ url.protocol = origin.protocol;
373
+ url.host = origin.host;
374
+ url.port = origin.port;
375
+ if (url.href === request.url)
376
+ return request;
377
+ const normalized = new Request(url, request);
378
+ originals.set(normalized, request);
379
+ return normalized;
380
+ }
381
+ };
382
+ }
383
+
384
+ // src/server/bun.ts
338
385
  function reclaimStaleUnixSocket(path) {
339
386
  const stats = statSync(path, { throwIfNoEntry: false });
340
387
  if (stats === undefined)
@@ -492,17 +539,21 @@ function createServer(config) {
492
539
  const handler = createHandler2(config);
493
540
  const consumerFetch = config.wrapFetch ? config.wrapFetch(handler) : handler;
494
541
  const admittedFetch = lifecycle.wrapFetch(consumerFetch);
542
+ const proxyOrigin = config.proxyOrigin && createProxyOrigin(config.proxyOrigin);
495
543
  const fetch = async (request, server) => {
544
+ const normalized = proxyOrigin && server ? proxyOrigin.normalize(request, server) : request;
545
+ if (normalized instanceof Response)
546
+ return normalized;
496
547
  if (socket && socketRoutePrefix) {
497
- const pathname = new URL(request.url).pathname;
548
+ const pathname = new URL(normalized.url).pathname;
498
549
  if (pathname.startsWith(socketRoutePrefix)) {
499
- return await socket.route.handler(request, {
550
+ return await socket.route.handler(normalized, {
500
551
  params: { socketPath: pathname.slice(socketRoutePrefix.length) },
501
552
  server
502
553
  });
503
554
  }
504
555
  }
505
- return admittedFetch(request, server);
556
+ return admittedFetch(normalized, server);
506
557
  };
507
558
  if (unixPath !== undefined) {
508
559
  reclaimStaleUnixSocket(unixPath);
@@ -538,6 +589,7 @@ function createServer(config) {
538
589
  });
539
590
  }
540
591
  const server = runtime;
592
+ proxyOrigin?.bind(server);
541
593
  return {
542
594
  url: server.url.toString().replace(/\/$/, ""),
543
595
  port: server.port ?? (unixPath !== undefined ? 0 : port),
@@ -4,7 +4,7 @@ import {
4
4
  AgentSandboxRestrictionSchema2,
5
5
  AgentSandboxGradeSchema2,
6
6
  AGENT_CODING_TOOL_NAMES
7
- } from "./index-bm180f2d.js";
7
+ } from "./index-ey15wx6j.js";
8
8
  import {
9
9
  AgentRecordIdSchema2,
10
10
  AgentRecordVersionSchema2,
@@ -19,7 +19,7 @@ import {
19
19
  appendEventIn,
20
20
  ArchiveProjectionSchema,
21
21
  createAgentRuntimeStore2
22
- } from "./index-mpg1t7nv.js";
22
+ } from "./index-0z3kfphz.js";
23
23
  import {
24
24
  AgentConversationPageSchema2,
25
25
  AgentConversationMessagePageSchema2
@@ -23,6 +23,62 @@ import {
23
23
  assertPositiveSafeInteger
24
24
  } from "./index-0s4xebbg.js";
25
25
 
26
+ // src/process/group-diagnostics.ts
27
+ import { z } from "zod";
28
+ var ObservationSchema = z.object({
29
+ failure: z.enum(["deadline", "observation"]),
30
+ groupId: z.number().int().positive(),
31
+ observations: z.number().int().nonnegative(),
32
+ lastStates: z.array(z.string().max(32)).max(16),
33
+ elapsedMs: z.number().int().nonnegative()
34
+ });
35
+ var observations = new WeakMap;
36
+
37
+ class GroupSettlementError extends Error {
38
+ constructor(message, observation, cause = observation) {
39
+ super(`${message}: ${JSON.stringify(observation)}`, { cause });
40
+ observations.set(this, { ...observation, lastStates: [...observation.lastStates] });
41
+ }
42
+ }
43
+ function ownData(value, key) {
44
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
45
+ return descriptor && "value" in descriptor ? descriptor.value : undefined;
46
+ }
47
+ function groupSettlementDiagnostics(cause) {
48
+ const pending = [{ value: cause, depth: 0 }];
49
+ const seen = new Set;
50
+ const found = [];
51
+ for (let visited = 0;pending.length && visited < 32 && found.length < 4; visited++) {
52
+ const entry = pending.pop();
53
+ if (!entry || seen.has(entry.value))
54
+ continue;
55
+ seen.add(entry.value);
56
+ try {
57
+ if (entry.value instanceof Error) {
58
+ const observation = observations.get(entry.value);
59
+ if (observation)
60
+ found.push(observation);
61
+ }
62
+ if (!(entry.value instanceof Error) || entry.depth >= 8)
63
+ continue;
64
+ if (entry.value instanceof AggregateError) {
65
+ const errors = ownData(entry.value, "errors");
66
+ if (Array.isArray(errors)) {
67
+ for (let index = 0;index < 16; index++) {
68
+ const value = ownData(errors, String(index));
69
+ if (value !== undefined)
70
+ pending.push({ value, depth: entry.depth + 1 });
71
+ }
72
+ }
73
+ }
74
+ const nested = ownData(entry.value, "cause");
75
+ if (nested !== undefined)
76
+ pending.push({ value: nested, depth: entry.depth + 1 });
77
+ } catch {}
78
+ }
79
+ return found;
80
+ }
81
+
26
82
  // src/process/group-presence.ts
27
83
  function processGroupExists(pid) {
28
84
  try {
@@ -57,36 +113,49 @@ function processGroups(timeoutMs) {
57
113
  return new Promise((resolve, reject) => {
58
114
  execFile("ps", ["-axo", "pgid=,stat="], { encoding: "utf8", timeout: timeoutMs, killSignal: "SIGKILL", maxBuffer: 1024 * 1024 }, (error, stdout) => {
59
115
  if (error)
60
- reject(new Error("Could not observe command process group settlement", { cause: error }));
116
+ reject(error);
61
117
  else
62
118
  resolve(stdout);
63
119
  });
64
120
  });
65
121
  }
66
- async function waitForProcessGroupExit(pid, deadline) {
122
+ async function waitForProcessGroupExit(pid, deadline, killLiveMembers) {
67
123
  if (pid === undefined)
68
124
  return;
69
125
  let observations = 0;
70
126
  let lastStates = [];
71
- for (;; ) {
72
- const remaining = Math.ceil(deadline - performance.now());
73
- if (remaining <= 0)
74
- throw new Error("Command process group settlement was not confirmed after SIGKILL", {
75
- cause: { groupId: pid, observations, lastStates }
76
- });
77
- if (!processGroupExists(pid))
78
- return;
79
- const table = await processGroups(remaining);
80
- observations++;
81
- if (!hasLiveGroupMember(table, pid))
82
- return;
83
- lastStates = table.split(/\r?\n/).flatMap((row) => {
84
- const fields = /^\s*(\d+)\s+(\S+)\s*$/.exec(row);
85
- return Number(fields?.[1]) === pid ? [fields?.[2] ?? "unknown"] : [];
86
- }).slice(0, 16);
87
- if (!processGroupExists(pid))
88
- return;
89
- await sleep(Math.min(5, Math.max(0, deadline - performance.now())));
127
+ const startedAt = performance.now();
128
+ const observation = () => ({
129
+ groupId: pid,
130
+ observations,
131
+ lastStates,
132
+ elapsedMs: Math.max(0, Math.ceil(performance.now() - startedAt))
133
+ });
134
+ try {
135
+ for (;; ) {
136
+ const remaining = Math.ceil(deadline - performance.now());
137
+ if (remaining <= 0)
138
+ throw new GroupSettlementError("Command process group settlement was not confirmed after SIGKILL", { ...observation(), failure: "deadline" });
139
+ if (!processGroupExists(pid))
140
+ return;
141
+ const table = await processGroups(remaining);
142
+ observations++;
143
+ if (!hasLiveGroupMember(table, pid))
144
+ return;
145
+ lastStates = table.split(/\r?\n/).flatMap((row) => {
146
+ const fields = /^\s*(\d+)\s+(\S+)\s*$/.exec(row);
147
+ return Number(fields?.[1]) === pid ? [(fields?.[2] ?? "unknown").slice(0, 32)] : [];
148
+ }).slice(0, 16);
149
+ if (!processGroupExists(pid))
150
+ return;
151
+ if (killLiveMembers && performance.now() < deadline && !await killLiveMembers())
152
+ return;
153
+ await sleep(Math.min(5, Math.max(0, deadline - performance.now())));
154
+ }
155
+ } catch (cause) {
156
+ if (cause instanceof GroupSettlementError)
157
+ throw cause;
158
+ throw new GroupSettlementError("Could not observe command process group settlement", { ...observation(), failure: "observation" }, cause);
90
159
  }
91
160
  }
92
161
 
@@ -151,7 +220,7 @@ async function killAndSettleGroup(pid, deadline, cleanupTimeoutMs) {
151
220
  const cleanupDeadline = Math.min(deadline, performance.now() + cleanupTimeoutMs);
152
221
  const signalled = await signalUntilSettled(pid, "SIGKILL", cleanupDeadline);
153
222
  if (signalled)
154
- await waitForProcessGroupExit(pid, cleanupDeadline);
223
+ await waitForProcessGroupExit(pid, cleanupDeadline, () => signalUntilSettled(pid, "SIGKILL", cleanupDeadline));
155
224
  return signalled;
156
225
  }
157
226
  async function stopCommandGroup(stop) {
@@ -235,7 +304,9 @@ async function stopLeaderAlone(stop) {
235
304
  return send("SIGKILL") || signalled;
236
305
  }
237
306
  function commandCleanupError(cause) {
238
- return new NativeCommandError2("COMMAND_CLEANUP", "Command cleanup did not complete", {
307
+ const observations = groupSettlementDiagnostics(cause);
308
+ const message = observations.length ? `Command cleanup did not complete: ${JSON.stringify(observations)}` : "Command cleanup did not complete";
309
+ return new NativeCommandError2("COMMAND_CLEANUP", message, {
239
310
  cause
240
311
  });
241
312
  }
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  CommandStopped,
9
9
  startNativeCommand
10
- } from "./index-e1sjcjfy.js";
10
+ } from "./index-7r4knwf8.js";
11
11
  import {
12
12
  NumericFileHandle,
13
13
  descriptorPath,
@@ -17,7 +17,7 @@ import {
17
17
  replaceMessage,
18
18
  ArchiveSpillSchema,
19
19
  createAgentRuntimeStore2
20
- } from "./index-mpg1t7nv.js";
20
+ } from "./index-0z3kfphz.js";
21
21
 
22
22
  // src/agent-runtime/store-memory.ts
23
23
  import { z as z2 } from "zod";
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ declare const ObservationSchema: z.ZodObject<{
3
+ failure: z.ZodEnum<{
4
+ deadline: "deadline";
5
+ observation: "observation";
6
+ }>;
7
+ groupId: z.ZodNumber;
8
+ observations: z.ZodNumber;
9
+ lastStates: z.ZodArray<z.ZodString>;
10
+ elapsedMs: z.ZodNumber;
11
+ }, z.core.$strip>;
12
+ type Observation = z.infer<typeof ObservationSchema>;
13
+ /** Only this internal error carries process-state diagnostics safe to promote. */
14
+ export declare class GroupSettlementError extends Error {
15
+ constructor(message: string, observation: Observation, cause?: unknown);
16
+ }
17
+ /** Bounded cause traversal; arbitrary caller messages never become cleanup diagnostics. */
18
+ export declare function groupSettlementDiagnostics(cause: unknown): Observation[];
19
+ export {};
20
+ //# sourceMappingURL=group-diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-diagnostics.d.ts","sourceRoot":"","sources":["../../src/process/group-diagnostics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,iBAAiB;;;;;;;;;iBAMrB,CAAC;AACH,KAAK,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAGrD,kFAAkF;AAClF,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,GAAE,OAAqB,EAKlF;CACF;AAQD,2FAA2F;AAC3F,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,EAAE,CA+BxE"}
@@ -1,5 +1,5 @@
1
1
  /** `killpg(..., 0)` also sees Linux zombies; only live members can still perform work. */
2
2
  export declare function hasLiveGroupMember(table: string, group: number): boolean;
3
3
  /** A delivered SIGKILL is asynchronous; do not report cleanup until every group member is dead. */
4
- export declare function waitForProcessGroupExit(pid: number | undefined, deadline: number): Promise<void>;
4
+ export declare function waitForProcessGroupExit(pid: number | undefined, deadline: number, killLiveMembers?: () => Promise<boolean>): Promise<void>;
5
5
  //# sourceMappingURL=group-settlement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"group-settlement.d.ts","sourceRoot":"","sources":["../../src/process/group-settlement.ts"],"names":[],"mappings":"AAIA,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAQxE;AAmBD,mGAAmG;AACnG,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
1
+ {"version":3,"file":"group-settlement.d.ts","sourceRoot":"","sources":["../../src/process/group-settlement.ts"],"names":[],"mappings":"AAKA,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAQxE;AAgBD,mGAAmG;AACnG,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,QAAQ,EAAE,MAAM,EAChB,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GACvC,OAAO,CAAC,IAAI,CAAC,CAmDf"}
@@ -1 +1 @@
1
- {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/process/group.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,KAAK,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAIpF,+FAA+F;AAC/F,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;IAYpE,MAAM;IACN,MAAM;EAIT;AAeD,gGAAgG;AAChG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CASpF;AAsCD,sGAAsG;AACtG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,6BAA6B,CAAC;IAC/C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB/E;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,gBAAgB,GAAG;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GAC/E,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,6BAA6B,CAAC;IACtC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,OAAO,CAAC,CAyBnB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAItE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAMxF"}
1
+ {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/process/group.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,KAAK,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAKpF,+FAA+F;AAC/F,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;IAYpE,MAAM;IACN,MAAM;EAIT;AAeD,gGAAgG;AAChG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CASpF;AAyCD,sGAAsG;AACtG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,6BAA6B,CAAC;IAC/C,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmB/E;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,gBAAgB,GAAG;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GAC/E,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,6BAA6B,CAAC;IACtC,UAAU,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,KAAK,EAAE,OAAO,CAAC;CAChB,GAAG,OAAO,CAAC,OAAO,CAAC,CAyBnB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAQtE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAMxF"}
@@ -1,3 +1,4 @@
1
+ import { type ProxyOriginConfig } from './proxy-origin.js';
1
2
  import { type ManagedServerHandle } from './shutdown.js';
2
3
  import type { SocketIOServerLifecycle } from './socket-io.js';
3
4
  import type { FetchComposition, FetchHandler, HandlerConfig, RawRoute, RawRouteContext } from './types.js';
@@ -26,6 +27,14 @@ export type UnixListenConfig = string | {
26
27
  };
27
28
  /** Bun-specific server config layered over the Fetch-clean handler config. */
28
29
  export interface BunServerConfig extends BunHandlerConfig, BunFetchComposition {
30
+ /**
31
+ * Restore the external scheme behind one trusted TCP ingress, before composition.
32
+ * The ingress must overwrite Host and X-Forwarded-Proto and remove Forwarded,
33
+ * X-Forwarded-Host and X-Forwarded-Port. Trusted requests require exactly one
34
+ * `http` or `https` proto; malformed/conflicting headers receive 400.
35
+ * Host/tenant authorization remains application-owned. Independent of trustProxy.
36
+ */
37
+ proxyOrigin?: ProxyOriginConfig;
29
38
  port?: number;
30
39
  hostname?: string;
31
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../src/server/bun.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC9C,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAC5D,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;AACtD,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAExD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,oBAAoB,GAAG,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC1D,KAAK,qBAAqB,GAAG,eAAe,SAAS;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,KAAK,CAAC;AAE3F,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,eAAe,EACf,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAChF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,8EAA8E;AAC9E,MAAM,WAAW,eAAgB,SAAQ,gBAAgB,EAAE,mBAAmB;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,SAAS,CAAC,EAAE,oBAAoB,CAAC;IACjC,iGAAiG;IACjG,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,GAAG,CAAC,EAAE,iBAAiB,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;AA+F7D,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAgMrE"}
1
+ {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../src/server/bun.ts"],"names":[],"mappings":"AAGA,OAAO,EAAqB,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC9C,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAC5D,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;AACtD,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAExD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,oBAAoB,GAAG,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC1D,KAAK,qBAAqB,GAAG,eAAe,SAAS;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,KAAK,CAAC;AAE3F,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,eAAe,EACf,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAChF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,8EAA8E;AAC9E,MAAM,WAAW,eAAgB,SAAQ,gBAAgB,EAAE,mBAAmB;IAC5E;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,SAAS,CAAC,EAAE,oBAAoB,CAAC;IACjC,iGAAiG;IACjG,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,GAAG,CAAC,EAAE,iBAAiB,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;AA+F7D,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAqMrE"}
@@ -0,0 +1,12 @@
1
+ import type { BunServer } from './bun.js';
2
+ /** One TCP ingress that overwrites Host/proto and strips competing URL headers. */
3
+ export interface ProxyOriginConfig {
4
+ /** Match the real socket peer, never a forwarded client IP. */
5
+ trustedPeer: (socketIp: string) => boolean;
6
+ }
7
+ /** Bun associates requestIP, timeout and upgrade with the original native request. */
8
+ export declare function createProxyOrigin(policy: ProxyOriginConfig): {
9
+ bind(server: BunServer): void;
10
+ normalize(request: Request, server: BunServer): Request | Response;
11
+ };
12
+ //# sourceMappingURL=proxy-origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-origin.d.ts","sourceRoot":"","sources":["../../src/server/proxy-origin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,mFAAmF;AACnF,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CAC5C;AAUD,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB;IAKvD,IAAI,SAAS,SAAS,GAAG,IAAI;IAQ7B,SAAS,UAAU,OAAO,UAAU,SAAS,GAAG,OAAO,GAAG,QAAQ;EAgCrE"}
package/llms/server.2.txt CHANGED
@@ -18,6 +18,137 @@ Also read:
18
18
  # Guide: HTTP server (docs/guide/server.md) (continued)
19
19
  ==============================================================================
20
20
 
21
+ ## Raw routes
22
+
23
+ Some routes cannot be a clean JSON contract — an OAuth redirect, a webhook with
24
+ signature verification, static files, the Socket.IO endpoint. `rawRoutes` are
25
+ plain `Request → Response` handlers, matched by the same router (shared CORS and
26
+ `onRequest`) but with no schema parsing and no `beforeHandle` gate — a raw route
27
+ authorises itself.
28
+
29
+ ```ts
30
+ createServer({
31
+ services,
32
+ rawRoutes: [
33
+ {
34
+ method: 'GET',
35
+ path: '/health',
36
+ handler: () => Response.json({ status: 'ok' }),
37
+ },
38
+ {
39
+ method: 'POST',
40
+ path: '/webhooks/:provider',
41
+ serviceName: 'webhooks',
42
+ action: 'receive',
43
+ handler: (req, ctx) => handleWebhook(ctx.params.provider, req),
44
+ },
45
+ ],
46
+ })
47
+ ```
48
+
49
+ `serviceName` and `action` are optional observability identity. Declare
50
+ `serviceName` when audit, logging, sampling or another cross-cutting policy
51
+ groups this raw route with a service; add `action` when the policy needs an
52
+ operation within that service. Stitchkit never derives either from `path`.
53
+ `action` without `serviceName`, and empty identity strings, fail at startup.
54
+
55
+ A path may be exact, carry `:param` segments, or end in `/*filePath` for a prefix
56
+ wildcard — and the two combine: `/app/:slug/*filePath` matches `/app/x/a/b` with
57
+ `ctx.params.slug === 'x'` and the remainder in `ctx.params.filePath` (a SPA
58
+ deep-link fallback). List more specific routes before the wildcard — the first
59
+ match wins. `staticRoute()` builds a raw route that serves a directory.
60
+
61
+ At startup Stitchkit rejects exact duplicates, equivalent parameter shapes
62
+ (`/users/:id` vs `/users/:userId`) and any later raw route completely hidden by
63
+ an earlier raw route. Partial overlap stays legal, including the recommended
64
+ specific-before-wildcard order. `GET` and `HEAD` remain independent because that
65
+ is how the actual raw matcher dispatches them. Raw-vs-contract overlap remains a
66
+ startup diagnostic naming the bypassed contract identity and scope.
67
+
68
+ ### Raw-route helpers
69
+
70
+ A raw route gives up the contract pipeline, so three things get re-implemented in
71
+ every one. `stitchkit/server` ships them — conveniences, not a second pipeline
72
+ (no auth, no schema gate beyond `parseBody`):
73
+
74
+ ```ts
75
+ import { respondJson, errorResponse, parseBody, badRequest } from 'stitchkit/server'
76
+
77
+ handler: async (req) => {
78
+ try {
79
+ const body = await parseBody(req, MySchema) // typed value, or null (no throw)
80
+ if (!body) throw badRequest('invalid body') // helpers THROW an AppError
81
+ return respondJson(await myService(body)) // JSON; 204 when null/undefined
82
+ } catch (err) {
83
+ return errorResponse(err) // same envelope as a contract route
84
+ }
85
+ }
86
+ ```
87
+
88
+ The error helpers (`badRequest`, `notFound`, …) **throw** an `AppError`, so raise
89
+ them inside the `try` and let `errorResponse(err)` render it — that runs any
90
+ thrown value through the framework's `normalizeError`, so a raw route returns the
91
+ **identical** `{ error: { code, message, … } }` shape (and `x-request-id`) a
92
+ contract route does.
93
+
94
+ ### Serving files & Range requests
95
+
96
+ `staticRoute` is basic on purpose — it reads the whole file into memory and has
97
+ no `Range` or conditional support; put a CDN in front, or use it for small web
98
+ assets. To serve **media** (video / audio / large downloads) that a browser must
99
+ seek and cache, use **`serveFile`** (Bun) — it streams the requested byte range
100
+ and speaks the conditional-request half of RFC 7233 / 9110:
101
+
102
+ ```ts
103
+ import { defineContract } from 'stitchkit/contract'
104
+ import { implement, serveFile } from 'stitchkit/server'
105
+ import { z } from 'zod'
106
+
107
+ const MediaParams = z.object({ id: z.string() })
108
+ const media = defineContract({ prefix: 'media' }, {
109
+ download: {
110
+ method: 'GET', path: '/:id', desc: 'Download media',
111
+ params: MediaParams, rawResponse: true, contentType: 'video/mp4',
112
+ },
113
+ inspect: {
114
+ method: 'HEAD', path: '/:id', desc: 'Inspect media',
115
+ params: MediaParams, rawResponse: true, contentType: 'video/mp4',
116
+ },
117
+ })
118
+
119
+ const mediaService = implement(media, {
120
+ download: ({ req, params }) =>
121
+ serveFile(req, { path: pathForId(params.id), filename: 'clip.mp4' }),
122
+ inspect: ({ req, params }) =>
123
+ serveFile(req, { path: pathForId(params.id), filename: 'clip.mp4' }),
124
+ })
125
+ ```
126
+
127
+ GET and HEAD are separate operations deliberately: declaring GET never creates
128
+ a hidden HEAD alias. Both travel through the normal contract router, params,
129
+ lifecycle/RBAC and request logging. A HEAD handler may inspect the raw query via
130
+ `ctx.req.url`, but cannot declare a request input schema or body.
131
+
132
+ `serveFile` returns `206` (range, with `Content-Range` + `Content-Length`), `200`
133
+ (full), `416` (unsatisfiable, `Content-Range: bytes */size`), `304`
134
+ (`If-None-Match` / `If-Modified-Since`), `404` (missing) or `405` (non GET/HEAD).
135
+ It always sets `Accept-Ranges: bytes`, a weak `ETag` and `Last-Modified` (so
136
+ `If-Range` keeps a changing file from being stitched from stale + fresh bytes),
137
+ and `nosniff`. `Content-Type` is auto-detected from the path — override it, or
138
+ pass `disposition` / `cacheControl` / `etag: false`, via the options.
139
+
140
+ Cross-origin, the browser lets JavaScript read only the CORS-safelisted response
141
+ headers. stitchkit therefore exposes the download-relevant ones by default
142
+ (`Content-Disposition`, `Content-Range`, `ETag`, …) — without that a `fetch`-based
143
+ download cannot recover the file's name. Override with `cors.exposeHeaders`
144
+ (extend `DEFAULT_CORS_EXPOSE_HEADERS` rather than replacing it), or pass `[]` to
145
+ emit none.
146
+
147
+ `serveFile` takes an explicit `path` and trusts it — **the caller owns
148
+ containment**. For a URL-derived path use `staticRoute` (which enforces it) or
149
+ `isWithinDir` first. The byte-range parser is exported on its own as
150
+ `parseByteRange(header, size)` for direct use and testing. → ADR 0023.
151
+
21
152
  ## Server primitives
22
153
 
23
154
  `stitchkit/server` also exports the primitives most APIs need. Each is a small,
package/llms/server.5.txt CHANGED
@@ -65,6 +65,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
65
65
  | `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
66
66
  | `HandlerConfig` | _type_ | config for `createHandler`, including optional `maxJsonBodyBytes`; bound to `BunServer` on this entrypoint |
67
67
  | `BunServerConfig` | _type_ | config for `createServer` (Bun); `unix` listens on a unix domain socket (mutually exclusive with `port`/`hostname`) — [details](../guide/server.md#local-daemon-over-a-unix-socket) |
68
+ | `ProxyOriginConfig` | _type_ | explicit real-peer trust for Bun `createServer({ proxyOrigin })`; strict external-scheme normalization before composition — [details](../guide/server.md#external-origin-behind-a-trusted-proxy) |
68
69
  | `UnixListenConfig` | _type_ | unix listener — a socket path, or `{ path, mode }` to tighten the file mode after listen |
69
70
  | `BunServerHandle` | _type_ | managed Bun handle (`url`, `port`, `runtime`, `status`, `shutdown`) |
70
71
  | `ManagedServerHandle` | _type_ | shared lifecycle shape generic over the runtime escape hatch |
package/llms/server.txt CHANGED
@@ -268,6 +268,7 @@ server. See [Testing & deployment](./testing-and-deployment.md).
268
268
  | `logging` | `true` for built-in request logs, or a `LoggingConfig` (see below) |
269
269
  | `traceId` | override per-request trace-id resolution — may return `undefined` to fall back |
270
270
  | `wrapFetch` | compose wrappers around the finished handler (request context, audit) |
271
+ | `proxyOrigin` | Bun TCP ingress policy: `{ trustedPeer(socketIp) }` restores the external scheme before all composition; independent of client-IP `trustProxy` |
271
272
  | `socket` | full Stitchkit Socket.IO handle; route, default WebSocket handler and shutdown are mounted once |
272
273
  | `websocket` | custom Bun WebSocket handler; with `socket`, this is the explicit composed handler |
273
274
  | `development` / `bun` | passthrough to `Bun.serve` |
@@ -276,6 +277,60 @@ Native Bun `routes` are intentionally not accepted: Bun matches them before
276
277
  `fetch`, so they could bypass shutdown admission. Use `rawRoutes`; they retain
277
278
  the Fetch `Request → Response` model and participate in lifecycle tracking.
278
279
 
280
+ ### External origin behind a trusted proxy
281
+
282
+ An HTTPS ingress often forwards plain HTTP to Bun. `trustProxy` trusts client-IP
283
+ headers only; it does not change `Request.url`. Opt into the separate URL policy:
284
+
285
+ ```ts
286
+ import { createServer, isLoopbackAddress } from 'stitchkit/server'
287
+
288
+ const server = createServer({
289
+ hostname: '127.0.0.1',
290
+ port: 3000,
291
+ proxyOrigin: { trustedPeer: isLoopbackAddress },
292
+ services,
293
+ })
294
+ ```
295
+
296
+ The predicate receives the **real TCP peer**, never `X-Forwarded-For` or
297
+ `X-Real-IP`. Match only the ingress you control. Loopback is appropriate only
298
+ when other local processes are trusted. Unmatched peers, missing peer metadata
299
+ and the default configuration ignore URL forwarding headers. Unix listeners have
300
+ no TCP peer and do not use this policy. This option belongs to Bun `createServer`,
301
+ not the Fetch-clean `createHandler` or Node `serveNode`.
302
+
303
+ The trusted ingress must overwrite `Host` with the external authority and
304
+ `X-Forwarded-Proto` with exactly `http` or `https`. It must remove `Forwarded`,
305
+ `X-Forwarded-Host` and `X-Forwarded-Port`; their presence, a missing/invalid
306
+ proto, a list of values or a malformed Host returns HTTP 400 before application
307
+ code. Header names are case-insensitive; proto values are lowercase. Multiple
308
+ ingress chains require a single sanitizing ingress at this boundary.
309
+
310
+ For an nginx HTTPS terminator, the relevant forwarding settings are:
311
+
312
+ ```nginx
313
+ proxy_set_header Host $http_host;
314
+ proxy_set_header X-Forwarded-Proto $scheme;
315
+ proxy_set_header Forwarded "";
316
+ proxy_set_header X-Forwarded-Host "";
317
+ proxy_set_header X-Forwarded-Port "";
318
+ ```
319
+
320
+ The normalized `Request.url` reaches `wrapFetch`, hooks, contracts, raw routes,
321
+ and the managed Socket.IO route. It preserves the path/query, wire authority
322
+ (including explicit ports), method, body, headers and linked cancellation signal.
323
+ Request cloning uses the normalized URL. The same runtime handle resolves
324
+ `requestIP`, `timeout` and `upgrade` against the original native request, retaining
325
+ WebSocket and shutdown behavior.
326
+
327
+ This is a scheme normalization policy, **not host authorization**. Applications
328
+ must retain their hostname/tenant allowlist and exact same-origin checks for
329
+ Origin and Referer. The TLS terminator must reject SNI/Host mismatches before
330
+ forwarding; a plain-HTTP Bun listener cannot observe the TLS SNI. The normalizer
331
+ does not accept `X-Forwarded-Host`, authorize a foreign Host, or treat HTTP and
332
+ HTTPS as aliases.
333
+
279
334
  ### Local daemon over a unix socket
280
335
 
281
336
  A local daemon whose door is a socket file needs no TCP port at all:
@@ -863,134 +918,3 @@ file: (ctx) => {
863
918
  `Response` is forwarded verbatim. Request headers are not relayed, so a `Range`
864
919
  sent to the proxy does not reach the origin and the full body comes back.
865
920
  → ADR 0038.
866
-
867
- ## Raw routes
868
-
869
- Some routes cannot be a clean JSON contract — an OAuth redirect, a webhook with
870
- signature verification, static files, the Socket.IO endpoint. `rawRoutes` are
871
- plain `Request → Response` handlers, matched by the same router (shared CORS and
872
- `onRequest`) but with no schema parsing and no `beforeHandle` gate — a raw route
873
- authorises itself.
874
-
875
- ```ts
876
- createServer({
877
- services,
878
- rawRoutes: [
879
- {
880
- method: 'GET',
881
- path: '/health',
882
- handler: () => Response.json({ status: 'ok' }),
883
- },
884
- {
885
- method: 'POST',
886
- path: '/webhooks/:provider',
887
- serviceName: 'webhooks',
888
- action: 'receive',
889
- handler: (req, ctx) => handleWebhook(ctx.params.provider, req),
890
- },
891
- ],
892
- })
893
- ```
894
-
895
- `serviceName` and `action` are optional observability identity. Declare
896
- `serviceName` when audit, logging, sampling or another cross-cutting policy
897
- groups this raw route with a service; add `action` when the policy needs an
898
- operation within that service. Stitchkit never derives either from `path`.
899
- `action` without `serviceName`, and empty identity strings, fail at startup.
900
-
901
- A path may be exact, carry `:param` segments, or end in `/*filePath` for a prefix
902
- wildcard — and the two combine: `/app/:slug/*filePath` matches `/app/x/a/b` with
903
- `ctx.params.slug === 'x'` and the remainder in `ctx.params.filePath` (a SPA
904
- deep-link fallback). List more specific routes before the wildcard — the first
905
- match wins. `staticRoute()` builds a raw route that serves a directory.
906
-
907
- At startup Stitchkit rejects exact duplicates, equivalent parameter shapes
908
- (`/users/:id` vs `/users/:userId`) and any later raw route completely hidden by
909
- an earlier raw route. Partial overlap stays legal, including the recommended
910
- specific-before-wildcard order. `GET` and `HEAD` remain independent because that
911
- is how the actual raw matcher dispatches them. Raw-vs-contract overlap remains a
912
- startup diagnostic naming the bypassed contract identity and scope.
913
-
914
- ### Raw-route helpers
915
-
916
- A raw route gives up the contract pipeline, so three things get re-implemented in
917
- every one. `stitchkit/server` ships them — conveniences, not a second pipeline
918
- (no auth, no schema gate beyond `parseBody`):
919
-
920
- ```ts
921
- import { respondJson, errorResponse, parseBody, badRequest } from 'stitchkit/server'
922
-
923
- handler: async (req) => {
924
- try {
925
- const body = await parseBody(req, MySchema) // typed value, or null (no throw)
926
- if (!body) throw badRequest('invalid body') // helpers THROW an AppError
927
- return respondJson(await myService(body)) // JSON; 204 when null/undefined
928
- } catch (err) {
929
- return errorResponse(err) // same envelope as a contract route
930
- }
931
- }
932
- ```
933
-
934
- The error helpers (`badRequest`, `notFound`, …) **throw** an `AppError`, so raise
935
- them inside the `try` and let `errorResponse(err)` render it — that runs any
936
- thrown value through the framework's `normalizeError`, so a raw route returns the
937
- **identical** `{ error: { code, message, … } }` shape (and `x-request-id`) a
938
- contract route does.
939
-
940
- ### Serving files & Range requests
941
-
942
- `staticRoute` is basic on purpose — it reads the whole file into memory and has
943
- no `Range` or conditional support; put a CDN in front, or use it for small web
944
- assets. To serve **media** (video / audio / large downloads) that a browser must
945
- seek and cache, use **`serveFile`** (Bun) — it streams the requested byte range
946
- and speaks the conditional-request half of RFC 7233 / 9110:
947
-
948
- ```ts
949
- import { defineContract } from 'stitchkit/contract'
950
- import { implement, serveFile } from 'stitchkit/server'
951
- import { z } from 'zod'
952
-
953
- const MediaParams = z.object({ id: z.string() })
954
- const media = defineContract({ prefix: 'media' }, {
955
- download: {
956
- method: 'GET', path: '/:id', desc: 'Download media',
957
- params: MediaParams, rawResponse: true, contentType: 'video/mp4',
958
- },
959
- inspect: {
960
- method: 'HEAD', path: '/:id', desc: 'Inspect media',
961
- params: MediaParams, rawResponse: true, contentType: 'video/mp4',
962
- },
963
- })
964
-
965
- const mediaService = implement(media, {
966
- download: ({ req, params }) =>
967
- serveFile(req, { path: pathForId(params.id), filename: 'clip.mp4' }),
968
- inspect: ({ req, params }) =>
969
- serveFile(req, { path: pathForId(params.id), filename: 'clip.mp4' }),
970
- })
971
- ```
972
-
973
- GET and HEAD are separate operations deliberately: declaring GET never creates
974
- a hidden HEAD alias. Both travel through the normal contract router, params,
975
- lifecycle/RBAC and request logging. A HEAD handler may inspect the raw query via
976
- `ctx.req.url`, but cannot declare a request input schema or body.
977
-
978
- `serveFile` returns `206` (range, with `Content-Range` + `Content-Length`), `200`
979
- (full), `416` (unsatisfiable, `Content-Range: bytes */size`), `304`
980
- (`If-None-Match` / `If-Modified-Since`), `404` (missing) or `405` (non GET/HEAD).
981
- It always sets `Accept-Ranges: bytes`, a weak `ETag` and `Last-Modified` (so
982
- `If-Range` keeps a changing file from being stitched from stale + fresh bytes),
983
- and `nosniff`. `Content-Type` is auto-detected from the path — override it, or
984
- pass `disposition` / `cacheControl` / `etag: false`, via the options.
985
-
986
- Cross-origin, the browser lets JavaScript read only the CORS-safelisted response
987
- headers. stitchkit therefore exposes the download-relevant ones by default
988
- (`Content-Disposition`, `Content-Range`, `ETag`, …) — without that a `fetch`-based
989
- download cannot recover the file's name. Override with `cors.exposeHeaders`
990
- (extend `DEFAULT_CORS_EXPOSE_HEADERS` rather than replacing it), or pass `[]` to
991
- emit none.
992
-
993
- `serveFile` takes an explicit `path` and trusts it — **the caller owns
994
- containment**. For a URL-derived path use `staticRoute` (which enforces it) or
995
- `isWithinDir` first. The byte-range parser is exported on its own as
996
- `parseByteRange(header, size)` for direct use and testing. → ADR 0023.
package/llms-full.txt CHANGED
@@ -1369,6 +1369,7 @@ server. See [Testing & deployment](./testing-and-deployment.md).
1369
1369
  | `logging` | `true` for built-in request logs, or a `LoggingConfig` (see below) |
1370
1370
  | `traceId` | override per-request trace-id resolution — may return `undefined` to fall back |
1371
1371
  | `wrapFetch` | compose wrappers around the finished handler (request context, audit) |
1372
+ | `proxyOrigin` | Bun TCP ingress policy: `{ trustedPeer(socketIp) }` restores the external scheme before all composition; independent of client-IP `trustProxy` |
1372
1373
  | `socket` | full Stitchkit Socket.IO handle; route, default WebSocket handler and shutdown are mounted once |
1373
1374
  | `websocket` | custom Bun WebSocket handler; with `socket`, this is the explicit composed handler |
1374
1375
  | `development` / `bun` | passthrough to `Bun.serve` |
@@ -1377,6 +1378,60 @@ Native Bun `routes` are intentionally not accepted: Bun matches them before
1377
1378
  `fetch`, so they could bypass shutdown admission. Use `rawRoutes`; they retain
1378
1379
  the Fetch `Request → Response` model and participate in lifecycle tracking.
1379
1380
 
1381
+ ### External origin behind a trusted proxy
1382
+
1383
+ An HTTPS ingress often forwards plain HTTP to Bun. `trustProxy` trusts client-IP
1384
+ headers only; it does not change `Request.url`. Opt into the separate URL policy:
1385
+
1386
+ ```ts
1387
+ import { createServer, isLoopbackAddress } from 'stitchkit/server'
1388
+
1389
+ const server = createServer({
1390
+ hostname: '127.0.0.1',
1391
+ port: 3000,
1392
+ proxyOrigin: { trustedPeer: isLoopbackAddress },
1393
+ services,
1394
+ })
1395
+ ```
1396
+
1397
+ The predicate receives the **real TCP peer**, never `X-Forwarded-For` or
1398
+ `X-Real-IP`. Match only the ingress you control. Loopback is appropriate only
1399
+ when other local processes are trusted. Unmatched peers, missing peer metadata
1400
+ and the default configuration ignore URL forwarding headers. Unix listeners have
1401
+ no TCP peer and do not use this policy. This option belongs to Bun `createServer`,
1402
+ not the Fetch-clean `createHandler` or Node `serveNode`.
1403
+
1404
+ The trusted ingress must overwrite `Host` with the external authority and
1405
+ `X-Forwarded-Proto` with exactly `http` or `https`. It must remove `Forwarded`,
1406
+ `X-Forwarded-Host` and `X-Forwarded-Port`; their presence, a missing/invalid
1407
+ proto, a list of values or a malformed Host returns HTTP 400 before application
1408
+ code. Header names are case-insensitive; proto values are lowercase. Multiple
1409
+ ingress chains require a single sanitizing ingress at this boundary.
1410
+
1411
+ For an nginx HTTPS terminator, the relevant forwarding settings are:
1412
+
1413
+ ```nginx
1414
+ proxy_set_header Host $http_host;
1415
+ proxy_set_header X-Forwarded-Proto $scheme;
1416
+ proxy_set_header Forwarded "";
1417
+ proxy_set_header X-Forwarded-Host "";
1418
+ proxy_set_header X-Forwarded-Port "";
1419
+ ```
1420
+
1421
+ The normalized `Request.url` reaches `wrapFetch`, hooks, contracts, raw routes,
1422
+ and the managed Socket.IO route. It preserves the path/query, wire authority
1423
+ (including explicit ports), method, body, headers and linked cancellation signal.
1424
+ Request cloning uses the normalized URL. The same runtime handle resolves
1425
+ `requestIP`, `timeout` and `upgrade` against the original native request, retaining
1426
+ WebSocket and shutdown behavior.
1427
+
1428
+ This is a scheme normalization policy, **not host authorization**. Applications
1429
+ must retain their hostname/tenant allowlist and exact same-origin checks for
1430
+ Origin and Referer. The TLS terminator must reject SNI/Host mismatches before
1431
+ forwarding; a plain-HTTP Bun listener cannot observe the TLS SNI. The normalizer
1432
+ does not accept `X-Forwarded-Host`, authorize a foreign Host, or treat HTTP and
1433
+ HTTPS as aliases.
1434
+
1380
1435
  ### Local daemon over a unix socket
1381
1436
 
1382
1437
  A local daemon whose door is a socket file needs no TCP port at all:
@@ -21245,6 +21300,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
21245
21300
  | `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
21246
21301
  | `HandlerConfig` | _type_ | config for `createHandler`, including optional `maxJsonBodyBytes`; bound to `BunServer` on this entrypoint |
21247
21302
  | `BunServerConfig` | _type_ | config for `createServer` (Bun); `unix` listens on a unix domain socket (mutually exclusive with `port`/`hostname`) — [details](../guide/server.md#local-daemon-over-a-unix-socket) |
21303
+ | `ProxyOriginConfig` | _type_ | explicit real-peer trust for Bun `createServer({ proxyOrigin })`; strict external-scheme normalization before composition — [details](../guide/server.md#external-origin-behind-a-trusted-proxy) |
21248
21304
  | `UnixListenConfig` | _type_ | unix listener — a socket path, or `{ path, mode }` to tighten the file mode after listen |
21249
21305
  | `BunServerHandle` | _type_ | managed Bun handle (`url`, `port`, `runtime`, `status`, `shutdown`) |
21250
21306
  | `ManagedServerHandle` | _type_ | shared lifecycle shape generic over the runtime escape hatch |
package/llms.txt CHANGED
@@ -23,7 +23,7 @@ Load the slice for each entrypoint your code imports — `import … from 'stitc
23
23
  - **stitchkit/remote**: [llms/remote.txt](llms/remote.txt) 1.0 KB
24
24
  - **stitchkit/contract**: [llms/contract.txt](llms/contract.txt) 31.9 KB
25
25
  - **stitchkit/primitives**: [llms/primitives.txt](llms/primitives.txt) 11.7 KB
26
- - **stitchkit/server**: [llms/server.txt](llms/server.txt) 45.3 KB → [llms/server.2.txt](llms/server.2.txt) 14.6 KB → [llms/server.3.txt](llms/server.3.txt) 45.6 KB → [llms/server.4.txt](llms/server.4.txt) 38.3 KB → [llms/server.5.txt](llms/server.5.txt) 22.1 KB
26
+ - **stitchkit/server**: [llms/server.txt](llms/server.txt) 42.2 KB → [llms/server.2.txt](llms/server.2.txt) 20.3 KB → [llms/server.3.txt](llms/server.3.txt) 45.6 KB → [llms/server.4.txt](llms/server.4.txt) 38.3 KB → [llms/server.5.txt](llms/server.5.txt) 22.3 KB
27
27
  - **stitchkit/observability**: [llms/observability.txt](llms/observability.txt) 45.9 KB
28
28
  - **stitchkit/agent-runtime**: [llms/agent-runtime.txt](llms/agent-runtime.txt) 42.7 KB → [llms/agent-runtime.2.txt](llms/agent-runtime.2.txt) 46.7 KB → [llms/agent-runtime.3.txt](llms/agent-runtime.3.txt) 35.6 KB
29
29
  - **stitchkit/agent-runtime/testing**: [llms/agent-runtime-testing.txt](llms/agent-runtime-testing.txt) 2.6 KB
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.110.5",
3
+ "version": "0.110.6",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",