stitchkit 0.103.0 → 0.103.2

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 (87) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/agent-runtime/browser-control-contract.d.ts +1118 -0
  3. package/dist/agent-runtime/browser-control-contract.d.ts.map +1 -0
  4. package/dist/agent-runtime/browser-controller.d.ts +31 -0
  5. package/dist/agent-runtime/browser-controller.d.ts.map +1 -0
  6. package/dist/agent-runtime/harness-control.d.ts.map +1 -1
  7. package/dist/agent-runtime/harness-realtime.d.ts +25 -0
  8. package/dist/agent-runtime/harness-realtime.d.ts.map +1 -0
  9. package/dist/agent-runtime/harness-tools.d.ts +11 -0
  10. package/dist/agent-runtime/harness-tools.d.ts.map +1 -0
  11. package/dist/agent-runtime/react.d.ts +4 -0
  12. package/dist/agent-runtime/react.d.ts.map +1 -0
  13. package/dist/agent-runtime/run-assertions.d.ts +63 -0
  14. package/dist/agent-runtime/run-assertions.d.ts.map +1 -0
  15. package/dist/entrypoints/agent-runtime/browser.d.ts +2 -0
  16. package/dist/entrypoints/agent-runtime/browser.d.ts.map +1 -1
  17. package/dist/entrypoints/agent-runtime/browser.js +235 -0
  18. package/dist/entrypoints/agent-runtime/coding-tools.js +3 -3
  19. package/dist/entrypoints/agent-runtime/harness-tools.d.ts +2 -0
  20. package/dist/entrypoints/agent-runtime/harness-tools.d.ts.map +1 -0
  21. package/dist/entrypoints/agent-runtime/harness-tools.js +32 -0
  22. package/dist/entrypoints/agent-runtime/harness.js +13 -228
  23. package/dist/entrypoints/agent-runtime/react.d.ts +2 -0
  24. package/dist/entrypoints/agent-runtime/react.d.ts.map +1 -0
  25. package/dist/entrypoints/agent-runtime/react.js +8 -0
  26. package/dist/entrypoints/agent-runtime/realtime.d.ts +2 -0
  27. package/dist/entrypoints/agent-runtime/realtime.d.ts.map +1 -0
  28. package/dist/entrypoints/agent-runtime/realtime.js +159 -0
  29. package/dist/entrypoints/agent-runtime/sandbox.js +3 -3
  30. package/dist/entrypoints/agent-runtime/testing.d.ts +1 -0
  31. package/dist/entrypoints/agent-runtime/testing.d.ts.map +1 -1
  32. package/dist/entrypoints/agent-runtime/testing.js +67 -2
  33. package/dist/entrypoints/agent-runtime.js +25 -20
  34. package/dist/entrypoints/cli.js +8 -8
  35. package/dist/entrypoints/index.js +15 -226
  36. package/dist/entrypoints/node.js +7 -5
  37. package/dist/entrypoints/observability.js +2 -2
  38. package/dist/entrypoints/react.js +2 -2
  39. package/dist/entrypoints/remote.js +3 -3
  40. package/dist/entrypoints/server.js +10 -8
  41. package/dist/entrypoints/testing.js +4 -4
  42. package/dist/entrypoints/tools/connections.js +2 -2
  43. package/dist/entrypoints/tools/invoker.js +6 -6
  44. package/dist/entrypoints/tools.js +25 -36
  45. package/dist/entrypoints/tracking.js +2 -2
  46. package/dist/{index-d82d3xg4.js → index-0c419yx7.js} +1 -1
  47. package/dist/{index-c6aagrbd.js → index-1br6gwhf.js} +4 -4
  48. package/dist/{index-xkhk5d6g.js → index-1khy8y7n.js} +5 -48
  49. package/dist/index-20yvtn9a.js +38 -0
  50. package/dist/{index-a9pd417t.js → index-5vyd0fkb.js} +1 -34
  51. package/dist/{index-r1z0rd9c.js → index-7n68nmc6.js} +1 -1
  52. package/dist/{index-zs4ms3th.js → index-90ppdx3q.js} +7 -30
  53. package/dist/{index-djjhhgjk.js → index-a4ajxwmb.js} +1 -1
  54. package/dist/index-c9d05kvt.js +11 -0
  55. package/dist/{index-ewbaywk5.js → index-dnn9z96q.js} +3 -3
  56. package/dist/index-jn9g8xdt.js +246 -0
  57. package/dist/index-kmpzj1cq.js +48 -0
  58. package/dist/{index-dvwj1f43.js → index-m2xqkv55.js} +2 -2
  59. package/dist/{index-m9fndta6.js → index-n2gre124.js} +3 -3
  60. package/dist/index-nggfa2yn.js +34 -0
  61. package/dist/{index-m79yr938.js → index-pw99k9h5.js} +1 -10
  62. package/dist/index-qapzv4p5.js +54 -0
  63. package/dist/index-r34mvse1.js +225 -0
  64. package/dist/{index-n9tyz8z3.js → index-r956rndn.js} +2 -2
  65. package/dist/index-srs8hj9c.js +10 -0
  66. package/dist/{index-jzczzcsb.js → index-tgzhq07n.js} +1 -1
  67. package/dist/index-vgkbcnkc.js +29 -0
  68. package/dist/{index-7z9ym7t8.js → index-x87j7csh.js} +35 -14
  69. package/dist/{index-3ct7hhq6.js → index-xwq9j1sf.js} +6 -56
  70. package/dist/{index-hby4qhax.js → index-yex7eg1v.js} +4 -10
  71. package/dist/{index-1h6gtdrj.js → index-ygkka6zn.js} +1 -1
  72. package/dist/index-ze1eph1k.js +20 -0
  73. package/dist/tools/schema/flatten-join.d.ts +1 -1
  74. package/dist/tools/schema/flatten-join.d.ts.map +1 -1
  75. package/dist/tools/schema/flatten.d.ts.map +1 -1
  76. package/llms/agent-runtime-browser.txt +26 -0
  77. package/llms/agent-runtime-harness-tools.txt +25 -0
  78. package/llms/agent-runtime-harness.txt +140 -1
  79. package/llms/agent-runtime-react.txt +21 -0
  80. package/llms/agent-runtime-realtime.txt +30 -0
  81. package/llms/agent-runtime-testing.txt +6 -0
  82. package/llms/stitchkit.txt +5 -1
  83. package/llms/tools.3.txt +1 -1
  84. package/llms/tools.txt +19 -1
  85. package/llms-full.txt +229 -3
  86. package/llms.txt +9 -5
  87. package/package.json +13 -1
@@ -0,0 +1,29 @@
1
+ import {
2
+ createBoundedSinkManager
3
+ } from "./index-h97tqqp1.js";
4
+ import {
5
+ AgentRuntimeEventSchema2
6
+ } from "./index-mnkk1ffz.js";
7
+
8
+ // src/agent-runtime/events.ts
9
+ function createAgentRuntimeEventSink2(config) {
10
+ const manager = createBoundedSinkManager({
11
+ write: config.write,
12
+ ...config.maxPending !== undefined && { maxPending: config.maxPending },
13
+ ...config.onSinkError && { onSinkError: config.onSinkError },
14
+ ...config.onDrop && { onDrop: config.onDrop }
15
+ });
16
+ return {
17
+ publish(rawEvent) {
18
+ const event = AgentRuntimeEventSchema2.parse(rawEvent);
19
+ const projected = config.project?.(event) ?? (config.project ? undefined : event);
20
+ if (projected)
21
+ manager.submit(() => AgentRuntimeEventSchema2.parse(projected));
22
+ },
23
+ flush: (bound) => manager.flush(bound),
24
+ getStatus: () => manager.getStatus(),
25
+ close: (bound) => manager.close(bound)
26
+ };
27
+ }
28
+
29
+ export { createAgentRuntimeEventSink2 };
@@ -308,7 +308,26 @@ function commonBaseType(schemas) {
308
308
  const types = normalizeTypes(nullable ? [...bases, "null"] : bases);
309
309
  return types.length > 0 ? { type: types } : {};
310
310
  }
311
- function mergePropertySchemas(properties) {
311
+ function describeVariants(properties, discriminator) {
312
+ const groups = new Map;
313
+ for (const { schema, labels } of properties) {
314
+ if (typeof schema.description !== "string")
315
+ continue;
316
+ const group = groups.get(schema.description) ?? [];
317
+ group.push(...labels);
318
+ groups.set(schema.description, group);
319
+ }
320
+ if (groups.size === 0)
321
+ return;
322
+ if (groups.size === 1 && properties.every(({ schema }) => typeof schema.description === "string")) {
323
+ return groups.keys().next().value;
324
+ }
325
+ return [...groups].map(([description, labels]) => ({
326
+ description,
327
+ labels: [...new Set(labels)].sort().join(" | ")
328
+ })).sort((left, right) => left.labels < right.labels ? -1 : left.labels > right.labels ? 1 : 0).map(({ description, labels }) => `When ${discriminator} = ${labels}: ${description}`).join(" ");
329
+ }
330
+ function mergePropertySchemas(properties, discriminator) {
312
331
  const schemas = properties.map((property) => nullableProjection(property.schema));
313
332
  const structural = schemas.map(withoutAnnotations);
314
333
  const [first] = structural;
@@ -318,13 +337,14 @@ function mergePropertySchemas(properties) {
318
337
  } else {
319
338
  const values = schemas.map(stringValues);
320
339
  const allStrings = values.every((value) => value !== null);
321
- merged = allStrings ? { type: "string", enum: [...new Set(values.flatMap((value) => value ?? []))] } : commonBaseType(structural);
322
- }
323
- const descriptions = [
324
- ...new Set(schemas.map((schema) => schema.description).filter((value) => typeof value === "string"))
325
- ];
326
- if (descriptions.length === 1)
327
- merged.description = descriptions[0];
340
+ merged = allStrings ? { type: "string", enum: [...new Set(values.flatMap((value) => value ?? []))].sort() } : commonBaseType(structural);
341
+ }
342
+ const description = describeVariants(properties.map((property, index) => ({
343
+ ...property,
344
+ schema: typeof property.schema.description === "string" ? property.schema : schemas[index] ?? property.schema
345
+ })), discriminator);
346
+ if (description !== undefined)
347
+ merged.description = description;
328
348
  return merged;
329
349
  }
330
350
 
@@ -434,20 +454,21 @@ function flattenNode(node) {
434
454
  });
435
455
  }
436
456
  const properties = {
437
- [discriminator.key]: mergePropertySchemas(discriminatorProperties)
457
+ [discriminator.key]: mergePropertySchemas(discriminatorProperties, discriminator.key)
438
458
  };
439
459
  const required = [discriminator.key];
440
- for (const [key, entries] of perKey) {
441
- let merged = mergePropertySchemas(entries);
460
+ for (const [key, entries] of [...perKey].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
461
+ let merged = mergePropertySchemas(entries, discriminator.key);
442
462
  const requiredEverywhere = entries.length === variants.length && entries.every((entry) => entry.required);
443
463
  if (requiredEverywhere) {
444
464
  required.push(key);
445
465
  } else {
446
466
  const requiredLabels = entries.filter((entry) => entry.required).flatMap((entry) => entry.labels);
447
467
  const presentLabels = entries.flatMap((entry) => entry.labels);
448
- const labels = requiredLabels.length > 0 ? requiredLabels : presentLabels;
449
- const verb = requiredLabels.length > 0 ? "Required" : "Available";
450
- merged = appendHint(merged, `${verb} if ${discriminator.key} = ${[...new Set(labels)].join(" | ")}`);
468
+ merged = appendHint(merged, `Available if ${discriminator.key} = ${[...new Set(presentLabels)].sort().join(" | ")}.`);
469
+ if (requiredLabels.length > 0) {
470
+ merged = appendHint(merged, `Required if ${discriminator.key} = ${[...new Set(requiredLabels)].sort().join(" | ")}`);
471
+ }
451
472
  }
452
473
  properties[key] = merged;
453
474
  }
@@ -17,7 +17,7 @@ import {
17
17
  } from "./index-b2bz0kdm.js";
18
18
  import {
19
19
  resolveTraceContext2
20
- } from "./index-hby4qhax.js";
20
+ } from "./index-yex7eg1v.js";
21
21
  import {
22
22
  DEFAULT_CONTRACT_STREAM_FRAME_BYTES2
23
23
  } from "./index-r5c4fedv.js";
@@ -25,13 +25,12 @@ import {
25
25
  joinRoutePath,
26
26
  parseTrailingWildcard
27
27
  } from "./index-m38ej7e5.js";
28
- import {
29
- createValidatedRealtimeSocket,
30
- isModuleNotFound
31
- } from "./index-m79yr938.js";
32
28
  import {
33
29
  RELEASE_HEADER2
34
30
  } from "./index-n15jny5b.js";
31
+ import {
32
+ isModuleNotFound
33
+ } from "./index-srs8hj9c.js";
35
34
  import {
36
35
  assertCorsConfig,
37
36
  isOriginAllowed,
@@ -50,7 +49,7 @@ import {
50
49
  getRequestContext2,
51
50
  setRequestEndpoint2,
52
51
  setRequestError2
53
- } from "./index-r1z0rd9c.js";
52
+ } from "./index-7n68nmc6.js";
54
53
  import {
55
54
  resolveTraceId2,
56
55
  resolveSocketIp2,
@@ -2341,55 +2340,6 @@ function bindProcessSignals2(handle, options = {}) {
2341
2340
  return { promise, close };
2342
2341
  }
2343
2342
 
2344
- // src/server/realtime.ts
2345
- function bindRealtimeServer2(contract, handle, options = {}) {
2346
- const outbound = (target) => createValidatedRealtimeSocket({
2347
- target,
2348
- inbound: {},
2349
- outbound: contract.serverToClient,
2350
- inboundDirection: "server-inbound",
2351
- outboundDirection: "server-outbound",
2352
- onRejected: options.onRejected,
2353
- logger: options.logger
2354
- });
2355
- const broadcast = outbound(handle.io);
2356
- return {
2357
- onConnection: (handler) => {
2358
- const listener = (raw) => {
2359
- const events = createValidatedRealtimeSocket({
2360
- target: raw,
2361
- inbound: contract.clientToServer,
2362
- outbound: contract.serverToClient,
2363
- inboundDirection: "server-inbound",
2364
- outboundDirection: "server-outbound",
2365
- onRejected: options.onRejected,
2366
- logger: options.logger
2367
- });
2368
- const failed = (error) => {
2369
- try {
2370
- raw.disconnect(true);
2371
- } catch {}
2372
- if (options.logger)
2373
- options.logger.error("Realtime connection handler failed", { error });
2374
- else
2375
- console.error("[stitchkit] realtime connection handler failed", error);
2376
- };
2377
- try {
2378
- Promise.resolve(handler({ raw, events, to: (room) => outbound(raw.to(room)) })).catch(failed);
2379
- } catch (error) {
2380
- failed(error);
2381
- }
2382
- };
2383
- handle.io.on("connection", listener);
2384
- return () => {
2385
- handle.io.off("connection", listener);
2386
- };
2387
- },
2388
- emit: broadcast.emit,
2389
- to: (room) => outbound(handle.io.to(room))
2390
- };
2391
- }
2392
-
2393
2343
  // src/server/websocket.ts
2394
2344
  function webSocketLane2(lane) {
2395
2345
  const { match, handlers } = lane;
@@ -3499,4 +3449,4 @@ function createUnixClientTransport2(config) {
3499
3449
  };
3500
3450
  }
3501
3451
 
3502
- export { contextContributionDelta, prepareContextContribution, mergeContextContribution, parseMultipart2, DEFAULT_STREAM_HEARTBEAT_MS2, streamingRoute2, ndjsonRoute2, sseRoute2, createHandler2, createScopedImplementRegistry2, implementRegistry2, createImplementRegistry2, bindProcessSignals2, bindRealtimeServer2, webSocketLane2, composeWebSocketHandlers2, createSocketIOServer2, socketIoLane2, UnixClientTransportError2, createUnixClientTransport2 };
3452
+ export { contextContributionDelta, prepareContextContribution, mergeContextContribution, parseMultipart2, DEFAULT_STREAM_HEARTBEAT_MS2, streamingRoute2, ndjsonRoute2, sseRoute2, createHandler2, createScopedImplementRegistry2, implementRegistry2, createImplementRegistry2, bindProcessSignals2, webSocketLane2, composeWebSocketHandlers2, createSocketIOServer2, socketIoLane2, UnixClientTransportError2, createUnixClientTransport2 };
@@ -1,12 +1,6 @@
1
- // src/internal/random-hex.ts
2
- function randomHex(bytes) {
3
- const arr = new Uint8Array(bytes);
4
- crypto.getRandomValues(arr);
5
- let hex = "";
6
- for (const byte of arr)
7
- hex += byte.toString(16).padStart(2, "0");
8
- return hex;
9
- }
1
+ import {
2
+ randomHex
3
+ } from "./index-c9d05kvt.js";
10
4
 
11
5
  // src/observability/trace.ts
12
6
  var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(.*)$/i;
@@ -80,4 +74,4 @@ function resolvePropagationContext2(metadata, fallback) {
80
74
  };
81
75
  }
82
76
 
83
- export { randomHex, createTraceContext2, parseTraceparent2, formatTraceparent2, resolveTraceContext2, childSpan2, resolvePropagationContext2 };
77
+ export { createTraceContext2, parseTraceparent2, formatTraceparent2, resolveTraceContext2, childSpan2, resolvePropagationContext2 };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createTraceContext2,
3
3
  formatTraceparent2
4
- } from "./index-hby4qhax.js";
4
+ } from "./index-yex7eg1v.js";
5
5
  import {
6
6
  isRecord,
7
7
  transportResult
@@ -0,0 +1,20 @@
1
+ // src/tools/lifecycle.ts
2
+ function composeToolLifecycle2(...hooks) {
3
+ return {
4
+ beforeHandle: async (ctx, endpoint) => {
5
+ for (const hook of hooks)
6
+ await hook?.beforeHandle?.(ctx, endpoint);
7
+ },
8
+ afterHandle: async (ctx, result, endpoint) => {
9
+ let current = result;
10
+ for (const hook of hooks) {
11
+ const transformed = await hook?.afterHandle?.(ctx, current, endpoint);
12
+ if (transformed !== undefined)
13
+ current = transformed;
14
+ }
15
+ return current;
16
+ }
17
+ };
18
+ }
19
+
20
+ export { composeToolLifecycle2 };
@@ -6,5 +6,5 @@ export interface VariantProperty {
6
6
  }
7
7
  /** The string members of a `const`/`enum`, or `null` when any member is not one. */
8
8
  export declare function stringValues(schema: ToolPresentationSchema): string[] | null;
9
- export declare function mergePropertySchemas(properties: VariantProperty[]): ToolPresentationSchema;
9
+ export declare function mergePropertySchemas(properties: VariantProperty[], discriminator: string): ToolPresentationSchema;
10
10
  //# sourceMappingURL=flatten-join.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"flatten-join.d.ts","sourceRoot":"","sources":["../../../src/tools/schema/flatten-join.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAExD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAsED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,EAAE,GAAG,IAAI,CAK5E;AAuID,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,sBAAsB,CA4B1F"}
1
+ {"version":3,"file":"flatten-join.d.ts","sourceRoot":"","sources":["../../../src/tools/schema/flatten-join.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAExD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAsED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,EAAE,GAAG,IAAI,CAK5E;AAsKD,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,eAAe,EAAE,EAC7B,aAAa,EAAE,MAAM,GACpB,sBAAsB,CAiCxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["../../../src/tools/schema/flatten.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAsJ7D;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,sBAAsB,GAAG,sBAAsB,CAG5F;AAED,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,GAAG,sBAAsB,CAG3F"}
1
+ {"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["../../../src/tools/schema/flatten.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA4J7D;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,sBAAsB,GAAG,sBAAsB,CAG5F;AAED,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,sBAAsB,GAAG,sBAAsB,CAG3F"}
@@ -7,6 +7,7 @@ Contains: API reference: `stitchkit/agent-runtime/browser` (docs/api/reference.
7
7
 
8
8
  Also read:
9
9
  - see `llms/agent-runtime.txt` for the Agent application runtime guide
10
+ - see `llms/agent-runtime-harness.txt` for the Agent composition guide
10
11
 
11
12
  ==============================================================================
12
13
  # API reference: `stitchkit/agent-runtime/browser` (docs/api/reference.md)
@@ -31,3 +32,28 @@ Use this entrypoint from client components and shared DTO packages. The full
31
32
  `stitchkit/agent-runtime` entrypoint remains server-only.
32
33
 
33
34
  ---
35
+
36
+ ### Browser control composition
37
+
38
+ `AgentBrowserRequestSchema` / `AgentBrowserRequest` derive from the trusted control
39
+ protocol but exclude runtime `context`. Submit permits only strict text/file parts;
40
+ file ownership and metadata policy are checked by server authorization. The shared
41
+ `agentControlRealtimeContract` owns `agent:control` acknowledgements and `agent:delivery`.
42
+
43
+ `createAgentController` / `AgentControllerConfig` take an existing Stitchkit
44
+ realtime `transport`, `conversationId`, `access` (`observe` or `control`), optional
45
+ `timeoutMs` (10000), `maxBufferedEvents` (256), `maxBufferedBytes` (1048576) and
46
+ `maxPendingRequests` (32). All bounds are positive safe integers. One controller owns one
47
+ attachment per conversation on that transport; share the controller between views.
48
+
49
+ `AgentController` exposes stable `subscribe`, `getSnapshot`, `request(command)` and async
50
+ `close`. `AgentBrowserCommand` uses the canonical operations, without request/conversation identity;
51
+ `submit` requires a caller-owned idempotency key. Unknown-outcome mutations are never
52
+ replayed automatically. `AgentControllerState` distinguishes connecting, ready,
53
+ disconnected, error and closed and retains the canonical full `view` plus safe error
54
+ code/message. Errors in automatic attach/refresh appear there. Reconnect attaches again
55
+ and reads a snapshot; snapshot gaps coalesce, stale generations cannot overwrite state,
56
+ and buffers/queues are bounded. Overflow or failed refresh is visible and requires
57
+ explicit recovery. Closing detaches without closing the shared transport or remote run.
58
+
59
+ See [agent composition](../guide/agent-composition.md) for a complete wiring example.
@@ -0,0 +1,25 @@
1
+ # stitchkit/agent-runtime/harness-tools — stitchkit docs slice
2
+
3
+ > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
+ > `llms.txt` in this package.
5
+
6
+ Contains: API reference: `stitchkit/agent-runtime/harness-tools` (docs/api/reference.md)
7
+
8
+ Also read:
9
+ - see `llms/agent-runtime.txt` for the Agent application runtime guide
10
+ - see `llms/agent-runtime-harness.txt` for the Agent composition guide
11
+
12
+ ==============================================================================
13
+ # API reference: `stitchkit/agent-runtime/harness-tools` (docs/api/reference.md)
14
+ ==============================================================================
15
+
16
+ ## `stitchkit/agent-runtime/harness-tools`
17
+
18
+ Optional evolving composition of the full `mountAgent` configuration with the runtime
19
+ fence. Its runtime peer is `ai`; its full tool presenter declarations also require
20
+ `@modelcontextprotocol/server`, as `stitchkit/tools` does. The base harness retains its
21
+ AI-only peer boundary. Existing custom tool callbacks need no new import.
22
+
23
+ | Export | Kind | Summary |
24
+ |--------|------|---------|
25
+ | `createAgentHarnessTools` / `AgentHarnessToolsConfig` | function / _type_ | async per-run full mount configuration (`services` plus all `AgentMountConfig` options); composes application lifecycle then runtime fence exactly once |
@@ -3,11 +3,150 @@
3
3
  > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
4
  > `llms.txt` in this package.
5
5
 
6
- Contains: API reference: `stitchkit/agent-runtime/harness` (docs/api/reference.md)
6
+ Contains: Guide: Agent composition (docs/guide/agent-composition.md) · API reference: `stitchkit/agent-runtime/harness` (docs/api/reference.md)
7
7
 
8
8
  Also read:
9
9
  - see `llms/agent-runtime.txt` for the Agent application runtime guide
10
10
 
11
+ ==============================================================================
12
+ # Guide: Agent composition (docs/guide/agent-composition.md)
13
+ ==============================================================================
14
+
15
+ ---
16
+ title: "Agent composition without a second runtime"
17
+ description: "Compose contract tools, authenticated realtime control, React views and exact-run behavioral checks."
18
+ type: architecture
19
+ status: active
20
+ created: 2026-09-30 22:45 +07:00
21
+ updated: 2026-09-30 23:03 +07:00
22
+ participants:
23
+ - role: authored
24
+ harness: Codex
25
+ model: GPT-6
26
+ at: 2026-09-30 22:45 +07:00
27
+ ---
28
+
29
+ # Compose an agent application
30
+
31
+ Use these pieces when an application wants the existing headless harness and a generic
32
+ conversation view. An application with its own AI SDK loop keeps `mountAgent`; a product
33
+ with custom message projections can keep its transport and UI. No consumer migration is
34
+ required. Model selection, storage, compaction, approvals and recovery stay with their
35
+ existing owners.
36
+
37
+ ## Resources and tools
38
+
39
+ ```ts
40
+ import {
41
+ createAgentHarnessFileResources,
42
+ createHeadlessAgentHarness,
43
+ } from 'stitchkit/agent-runtime/harness';
44
+ import { createAgentHarnessTools } from 'stitchkit/agent-runtime/harness-tools';
45
+
46
+ const resources = createAgentHarnessFileResources({
47
+ roots: [{ id: 'guide', kind: 'instruction', path: instructionRoot }],
48
+ limits: { maxTotalBytes: 256 * 1024 },
49
+ });
50
+ const harness = createHeadlessAgentHarness({
51
+ protocol, store, models, promptBudget,
52
+ resources,
53
+ tools: createAgentHarnessTools(({ context, run }) => ({
54
+ services: [catalogService],
55
+ context: { ...context, messageId: run.assistantMessageId },
56
+ runtimeTools: resources.runtimeTools,
57
+ lifecycle: authorizeTools,
58
+ hooks: observeTools,
59
+ })),
60
+ });
61
+ ```
62
+
63
+ The factory composes application authorization then the runtime fence through `mountAgent`.
64
+ The optional harness-tools leaf preserves the complete tool types, including MCP presenter
65
+ declarations; install the same `ai` and `@modelcontextprotocol/server` peers as for
66
+ `stitchkit/tools`. The base harness stays AI-only. Every mount option remains available, including registry, runtime tools, extension,
67
+ coercion, output stripping, hooks and durability. Async per-run configuration is supported.
68
+ The original tools callback still accepts arbitrary typed AI SDK tools and prepareStep.
69
+ The resource reader is explicitly selected: filtered allowlists do not gain tools merely
70
+ because a loader exists. Root IDs, kinds, provenance and limits are unchanged.
71
+
72
+ ## Server and browser
73
+
74
+ ```ts
75
+ import { bindAgentHarnessRealtime } from 'stitchkit/agent-runtime/realtime';
76
+
77
+ const binding = bindAgentHarnessRealtime(harness, socket, {
78
+ async authorize({ identity, request, signal }) {
79
+ // socket.data comes from your verified Socket.IO handshake, not a browser context.
80
+ if (!await mayAccessConversation(identity, request, signal)) return null;
81
+ return { context: { userId: identity.userId } };
82
+ },
83
+ onError: reportServerError,
84
+ });
85
+ ```
86
+
87
+ `socket` is the existing `createSocketIOServer` handle, attached to `createServer` (Bun)
88
+ or the Node adapter. The access policy checks every operation, file/reference and metadata
89
+ as well as snapshot/event reads. Session handshake authentication alone is insufficient.
90
+ Conversation ownership and observer/controller roles are application policy; the canonical
91
+ control server additionally enforces its exclusive controller lease.
92
+
93
+ ```ts
94
+ import { createSocketIOClient } from 'stitchkit';
95
+ import { createAgentController } from 'stitchkit/agent-runtime/browser';
96
+ import { useAgent } from 'stitchkit/agent-runtime/react';
97
+
98
+ const transport = createSocketIOClient({ url: serverUrl, auth: sessionAuth });
99
+ const agent = createAgentController({ transport, conversationId, access: 'control' });
100
+ transport.connect();
101
+
102
+ function Conversation() {
103
+ const state = useAgent(agent);
104
+ const conversation = state.view.conversations[conversationId];
105
+ // Render canonical messages/parts, transient progress, approvals and state.error.
106
+ return <Transcript conversation={conversation} status={state.status} error={state.error} />;
107
+ }
108
+
109
+ await agent.request({
110
+ operation: 'submit', idempotencyKey: crypto.randomUUID(),
111
+ parts: [{ type: 'text', text: 'Find available items' }],
112
+ });
113
+ await agent.request({ operation: 'interrupt', runId });
114
+ await agent.request({ operation: 'respond-approval', approvalId, approved: true });
115
+ await agent.close(); // leaves transport and harness alive
116
+ ```
117
+
118
+ Wait for `ready` before sending. Share a controller among views of the same conversation;
119
+ create it at session scope, not during React render. Unmount only unsubscribes the view.
120
+ To release the session, await `close`; to stop the application, separately close the
121
+ binding, transport and harness through the application's shutdown lifecycle. Closing a
122
+ view does not interrupt its remote run. Use explicit `interrupt` for cancellation.
123
+
124
+ Reconnect reattaches and reads durable state. Mutations are not retried automatically:
125
+ if an acknowledgement is lost, retain the original idempotency key. Incoming event
126
+ buffers and outgoing requests have finite limits; capacity/overflow/lease failures stay
127
+ visible. A server overflow deliberately disconnects the transport; reconnect explicitly.
128
+
129
+ ## Check behavior, not just admission
130
+
131
+ ```ts
132
+ import { inspectAgentRun } from 'stitchkit/agent-runtime/testing';
133
+
134
+ const ticket = harness.submit(input);
135
+ const admitted = await ticket.admission;
136
+ await ticket.result;
137
+ const result = inspectAgentRun(await harness.snapshot(input.conversationId), admitted.runId);
138
+ result.calledTool('catalog_search');
139
+ result.toolSucceeded('catalog_search');
140
+ result.notCalledTool('catalog_delete');
141
+ result.completed();
142
+ ```
143
+
144
+ `calledTool` proves a call record (including an approved continuation linked through that
145
+ run’s exact input messages); `toolSucceeded` also needs a matching successful result.
146
+ `completed` requires successful terminal output with no pending approval. Prior runs,
147
+ provider/policy stops, failed calls and unfinished runs cannot satisfy those assertions.
148
+ The helper does not invoke a model or poll; the existing runtime ticket owns execution.
149
+
11
150
  ==============================================================================
12
151
  # API reference: `stitchkit/agent-runtime/harness` (docs/api/reference.md)
13
152
  ==============================================================================
@@ -0,0 +1,21 @@
1
+ # stitchkit/agent-runtime/react — stitchkit docs slice
2
+
3
+ > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
+ > `llms.txt` in this package.
5
+
6
+ Contains: API reference: `stitchkit/agent-runtime/react` (docs/api/reference.md)
7
+
8
+ Also read:
9
+ - see `llms/agent-runtime.txt` for the Agent application runtime guide
10
+ - see `llms/agent-runtime-harness.txt` for the Agent composition guide
11
+
12
+ ==============================================================================
13
+ # API reference: `stitchkit/agent-runtime/react` (docs/api/reference.md)
14
+ ==============================================================================
15
+
16
+ ## `stitchkit/agent-runtime/react`
17
+
18
+ Optional evolving React peer leaf. `useAgent` takes a controller and subscribes through
19
+ `useSyncExternalStore`, returning the same `AgentControllerState` as `getSnapshot()`.
20
+ Create and close the controller at application/session scope. Multiple components can
21
+ observe it; StrictMode and unmount release only the component's own subscription.
@@ -0,0 +1,30 @@
1
+ # stitchkit/agent-runtime/realtime — stitchkit docs slice
2
+
3
+ > Generated from the `docs/` tree by `bun run gen:llms`. The index of all slices is
4
+ > `llms.txt` in this package.
5
+
6
+ Contains: API reference: `stitchkit/agent-runtime/realtime` (docs/api/reference.md)
7
+
8
+ Also read:
9
+ - see `llms/agent-runtime.txt` for the Agent application runtime guide
10
+ - see `llms/agent-runtime-harness.txt` for the Agent composition guide
11
+
12
+ ==============================================================================
13
+ # API reference: `stitchkit/agent-runtime/realtime` (docs/api/reference.md)
14
+ ==============================================================================
15
+
16
+ ## `stitchkit/agent-runtime/realtime`
17
+
18
+ Optional evolving Socket.IO server binding. `bindAgentHarnessRealtime` takes `(harness, socket, config)` and returns `close()` for its subscriptions and control connections, without closing
19
+ socket or harness. `AgentHarnessRealtimeConfig` requires `authorize({ identity, request,
20
+ signal })`: return `{ context }` after verifying the session principal, conversation,
21
+ operation, files and metadata, or `null`. Identity is `socket.data` from the application's
22
+ verified handshake. Every request and event delivery is authorized. Observe access does
23
+ not grant mutation; controller leases and approval policy still belong to the harness.
24
+
25
+ `maxPendingEvents` and `maxPendingAttachments` forward to the canonical control server;
26
+ `maxPendingRequests` bounds in-flight authorization/control requests per socket (32);
27
+ `authorizationTimeoutMs` bounds each authorization (10000 ms). Honor its AbortSignal.
28
+ `onError` receives internal errors; peers receive generic denials. Revoking a conversation
29
+ releases only its attachment. Event overflow closes the socket; explicitly reconnect the
30
+ application transport. A transport's Engine.IO payload limit remains application-owned.
@@ -7,6 +7,7 @@ Contains: API reference: `stitchkit/agent-runtime/testing` (docs/api/reference.
7
7
 
8
8
  Also read:
9
9
  - see `llms/agent-runtime.txt` for the Agent application runtime guide
10
+ - see `llms/agent-runtime-harness.txt` for the Agent composition guide
10
11
 
11
12
  ==============================================================================
12
13
  # API reference: `stitchkit/agent-runtime/testing` (docs/api/reference.md)
@@ -28,3 +29,8 @@ Credential-free provider and concurrency fixtures for Bun and Node tests.
28
29
  | `runAgentStoreConformance` | function | reusable black-box duplicate/coalescing/stale/recovery contract for durable agent-store adapters |
29
30
  | `AgentStoreConformanceConfig` | _type_ | `{ createStore(context), cleanup?(context) }` — the factory the contract runs against, plus a teardown that runs once whether the scenario passed or failed |
30
31
  | `AgentStoreConformanceContext` | _type_ | `{ conversationIds }` — every conversation the scenario will mutate, handed over **before** the first mutation so an adapter can provision application-owned parent rows; a zero-argument factory stays valid |
32
+
33
+ `inspectAgentRun` takes `(snapshot, runId)` and returns `completed()`, `calledTool(name)`,
34
+ `notCalledTool(name)` and `toolSucceeded(name)` assertions over durable evidence for
35
+ that exact run. Completion requires terminal reason `success`, not `policy_stop` or
36
+ `provider_stop`, and no pending approval. Successful tools require matching call/result IDs.
@@ -90,7 +90,10 @@ own, recorded as an ADR.
90
90
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
91
91
  | `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 |
92
92
  | `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
93
- | `stitchkit/agent-runtime/browser` | browser + server | evolving | canonical agent records, events and reconnect cursor without execution or sinks |
93
+ | `stitchkit/agent-runtime/browser` | browser + server | evolving | canonical agent records, events, reconnect cursor and optional conversation controller |
94
+ | `stitchkit/agent-runtime/harness-tools` | server | evolving | full mountAgent configuration with the current run fence |
95
+ | `stitchkit/agent-runtime/react` | browser + server | evolving | React subscription to an application-owned agent controller |
96
+ | `stitchkit/agent-runtime/realtime` | server (Bun or Node) | evolving | authorized agent control over the existing Socket.IO server |
94
97
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
95
98
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
96
99
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
@@ -200,6 +203,7 @@ map — feature → packages:
200
203
  | MCP / agent adapters (`stitchkit/tools`) | `@modelcontextprotocol/server` `ai` |
201
204
  | Agent application runtime (`stitchkit/agent-runtime`) | `ai` |
202
205
  | Headless Agent harness (`stitchkit/agent-runtime/harness`) | `ai` |
206
+ | Harness tools (`stitchkit/agent-runtime/harness-tools`) | `ai` `@modelcontextprotocol/server` (tool declarations) |
203
207
  | Agent coding tools (`stitchkit/agent-runtime/coding-tools`) | — |
204
208
  | OpenRouter runtime adapter (`stitchkit/agent-runtime/openrouter`) | `ai` `@openrouter/ai-sdk-provider` |
205
209
  | SQLite agent store (`stitchkit/agent-runtime/sqlite/bun` or `/node`) | — (runtime built-in) |
package/llms/tools.3.txt CHANGED
@@ -280,7 +280,7 @@ Advanced building blocks — the shared machinery the mounts are built on.
280
280
  | `ToolSurfaceDefinition` | _type_ | shared object-shaped `{ services?, runtimeTools? }` introspection surface |
281
281
  | `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
282
282
  | `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
283
- | `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions into conservative object joins; divergent fields retain every provable base kind in a deterministic `type` array, and the projection never executes validation |
283
+ | `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 |
284
284
  | `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
285
285
  | `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
286
286
  | `ToolManifestEntry` | _type_ | one `buildToolManifest` row |
package/llms/tools.txt CHANGED
@@ -714,7 +714,25 @@ Capable models (Claude, Gemini, GPT) handle that; weaker / cheaper ones can drop
714
714
  the field or mangle its strings. Set `flattenUnionInput: true` (on
715
715
  `createMcpHandler` / `mountMcp` / `mountAgent`) to advertise each discriminated
716
716
  union as a **single flat object** instead — the discriminator becomes an enum and
717
- each variant's fields become optional with a `Required if <disc> = …` hint.
717
+ each field required by every variant stays required. Other fields become optional
718
+ with `Available if <disc> = …` and, where applicable, `Required if <disc> = …` hints.
719
+
720
+ **Branch explanations keep their meaning.** A shared description appears once;
721
+ different descriptions are grouped by text and qualified with `When <disc> = …:`.
722
+ A branch without a description does not inherit another branch's explanation.
723
+ For example, an optional object `value` in `single` and a required array `value`
724
+ in `group` advertise:
725
+
726
+ ```text
727
+ When kind = group: Two to ten references.
728
+ When kind = single: One reference.
729
+ Available if kind = group | single. Required if kind = group
730
+ ```
731
+
732
+ A third branch without `value` is absent from those labels. The object and array
733
+ retain their separate shapes; the array's minimum length does not apply to the object.
734
+ Discriminator values, branch explanation labels and joined property order are
735
+ deterministic under branch reordering. → ADR [0221](../decisions/0221-flat-field-explanations-retain-branch-meaning.md).
718
736
 
719
737
  **A field several variants declare keeps its type.** Flattening puts every
720
738
  variant's fields side by side, so a key two variants share has to be advertised