stitchkit 0.100.0 → 0.100.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 (79) hide show
  1. package/CHANGELOG.md +93 -1
  2. package/dist/agent-runtime/invocation-attempt.d.ts +6 -0
  3. package/dist/agent-runtime/invocation-attempt.d.ts.map +1 -1
  4. package/dist/agent-runtime/invocation-payload.d.ts +2 -0
  5. package/dist/agent-runtime/invocation-payload.d.ts.map +1 -1
  6. package/dist/agent-runtime/invocation-read.d.ts.map +1 -1
  7. package/dist/agent-runtime/invocations.d.ts +6 -0
  8. package/dist/agent-runtime/invocations.d.ts.map +1 -1
  9. package/dist/agent-runtime/projections.d.ts.map +1 -1
  10. package/dist/agent-runtime/schedule-dispatch.d.ts +9 -2
  11. package/dist/agent-runtime/schedule-dispatch.d.ts.map +1 -1
  12. package/dist/agent-runtime/schedule-records.d.ts +8 -2
  13. package/dist/agent-runtime/schedule-records.d.ts.map +1 -1
  14. package/dist/agent-runtime/schedules.d.ts +5 -0
  15. package/dist/agent-runtime/schedules.d.ts.map +1 -1
  16. package/dist/agent-runtime/sqlite-events.d.ts.map +1 -1
  17. package/dist/agent-runtime/store-create.d.ts.map +1 -1
  18. package/dist/agent-runtime/store-driver-contract.d.ts +9 -0
  19. package/dist/agent-runtime/store-driver-contract.d.ts.map +1 -1
  20. package/dist/agent-runtime/store-event-once.d.ts +9 -1
  21. package/dist/agent-runtime/store-event-once.d.ts.map +1 -1
  22. package/dist/agent-runtime/store-memory.d.ts.map +1 -1
  23. package/dist/agent-runtime/store.d.ts +6 -0
  24. package/dist/agent-runtime/store.d.ts.map +1 -1
  25. package/dist/application/change-subscription.d.ts.map +1 -1
  26. package/dist/application/notification-delivery.d.ts +24 -1
  27. package/dist/application/notification-delivery.d.ts.map +1 -1
  28. package/dist/application/notification-outbox-state.d.ts +4 -0
  29. package/dist/application/notification-outbox-state.d.ts.map +1 -1
  30. package/dist/application/notification-outbox.d.ts.map +1 -1
  31. package/dist/application/process-lifecycle-transitions.d.ts +13 -2
  32. package/dist/application/process-lifecycle-transitions.d.ts.map +1 -1
  33. package/dist/application/process-lifecycle.d.ts +2 -1
  34. package/dist/application/process-lifecycle.d.ts.map +1 -1
  35. package/dist/application/watch-hub-core.d.ts +14 -79
  36. package/dist/application/watch-hub-core.d.ts.map +1 -1
  37. package/dist/application/watch-hub-source.d.ts +98 -0
  38. package/dist/application/watch-hub-source.d.ts.map +1 -0
  39. package/dist/browser/session-credentials.d.ts +6 -0
  40. package/dist/browser/session-credentials.d.ts.map +1 -1
  41. package/dist/browser/session.d.ts +3 -1
  42. package/dist/browser/session.d.ts.map +1 -1
  43. package/dist/entrypoints/agent-runtime/harness.js +1 -1
  44. package/dist/entrypoints/agent-runtime/sqlite/bun.js +2 -2
  45. package/dist/entrypoints/agent-runtime/sqlite/node.js +2 -2
  46. package/dist/entrypoints/agent-runtime/testing.js +2 -2
  47. package/dist/entrypoints/agent-runtime.js +53 -19
  48. package/dist/entrypoints/application/diagnostic-journal.js +1 -1
  49. package/dist/entrypoints/application.d.ts +2 -2
  50. package/dist/entrypoints/application.d.ts.map +1 -1
  51. package/dist/entrypoints/application.js +285 -193
  52. package/dist/entrypoints/files.js +8 -2
  53. package/dist/entrypoints/index.js +33 -6
  54. package/dist/entrypoints/live.js +16 -12
  55. package/dist/entrypoints/node.js +1 -1
  56. package/dist/entrypoints/server.js +4 -4
  57. package/dist/{index-s84he4v7.js → index-10x2hbx0.js} +4 -1
  58. package/dist/{index-js7gqrb1.js → index-c6bxbsnc.js} +5 -1
  59. package/dist/{index-y9qn426m.js → index-eaysmdtq.js} +10 -1
  60. package/dist/{index-xwhjjw2x.js → index-f4fx2tza.js} +119 -49
  61. package/dist/{index-p3vsv1fb.js → index-hadmp193.js} +14 -5
  62. package/dist/{index-0b1cnze2.js → index-vqr7f7v8.js} +31 -15
  63. package/dist/internal/coalesced-task.d.ts +2 -0
  64. package/dist/internal/coalesced-task.d.ts.map +1 -1
  65. package/dist/internal/process-instance.d.ts.map +1 -1
  66. package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
  67. package/dist/server/realtime.d.ts.map +1 -1
  68. package/llms/agent-runtime.2.txt +13 -5
  69. package/llms/agent-runtime.3.txt +9 -4
  70. package/llms/agent-runtime.txt +8 -3
  71. package/llms/application.2.txt +47 -6
  72. package/llms/application.txt +18 -27
  73. package/llms/live.txt +2 -1
  74. package/llms/server.3.txt +22 -0
  75. package/llms/stitchkit.3.txt +9 -5
  76. package/llms/upgrading-0.74.0-0.100.0.txt +15 -3
  77. package/llms-full.txt +118 -29
  78. package/llms.txt +5 -5
  79. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  attemptExclusiveLock
3
- } from "../index-js7gqrb1.js";
3
+ } from "../index-c6bxbsnc.js";
4
4
  import"../index-zjjyynm3.js";
5
5
  import {
6
6
  ManagedFileError,
@@ -47,6 +47,12 @@ function describeHolder(holder) {
47
47
  const machine = holder.machine === undefined ? "" : `, machine ${holder.machine}`;
48
48
  return `pid ${holder.pid} on ${holder.host}${machine} since ${holder.acquiredAt}`;
49
49
  }
50
+ function describeEvidence(diagnosis) {
51
+ if (!diagnosis)
52
+ return "";
53
+ const identity = diagnosis.identity === undefined ? "" : `, identity ${diagnosis.identity}`;
54
+ return ` (${diagnosis.attribution}, liveness ${diagnosis.liveness}${identity})`;
55
+ }
50
56
  function pause(ms, signal) {
51
57
  return new Promise((resolve) => {
52
58
  const done = () => {
@@ -94,7 +100,7 @@ async function withExclusiveLock(path, run, options = {}) {
94
100
  const remaining = deadline - performance.now();
95
101
  if (remaining <= 0) {
96
102
  const holder = attempt.diagnosis?.owner ?? null;
97
- throw new ExclusiveLockError("LOCK_TIMEOUT", label, holder, `[stitchkit] the lock on "${label}" is held by ${describeHolder(holder)}; gave up after ${timeoutMs} ms`, { cause: attempt.error });
103
+ throw new ExclusiveLockError("LOCK_TIMEOUT", label, holder, `[stitchkit] the lock on "${label}" is held by ${describeHolder(holder)}${describeEvidence(attempt.diagnosis)}; gave up after ${timeoutMs} ms`, { cause: attempt.error });
98
104
  }
99
105
  await pause(Math.min(delay * (0.5 + Math.random() / 2), remaining), signal);
100
106
  delay = Math.min(delay * 2, MAX_RETRY_MS);
@@ -32,6 +32,9 @@ import {
32
32
  RealtimeRequestRejectedError,
33
33
  RealtimeRequestTimeoutError
34
34
  } from "../index-dce5wxcq.js";
35
+ import {
36
+ withDeadline
37
+ } from "../index-b9aefrqd.js";
35
38
  import"../index-48ffdxgk.js";
36
39
  import {
37
40
  createContractFactory,
@@ -398,8 +401,8 @@ function createRetainedTopics() {
398
401
  }
399
402
  // src/browser/session.ts
400
403
  class SessionExpiredError extends Error {
401
- constructor() {
402
- super("The originating session is no longer active");
404
+ constructor(options) {
405
+ super("The originating session is no longer active", options);
403
406
  this.name = "SessionExpiredError";
404
407
  }
405
408
  }
@@ -446,7 +449,8 @@ function createSessionScope() {
446
449
  assertCurrent();
447
450
  return result;
448
451
  } catch (error) {
449
- assertCurrent();
452
+ if (!current())
453
+ throw new SessionExpiredError({ cause: error });
450
454
  throw error;
451
455
  }
452
456
  },
@@ -500,7 +504,30 @@ function createSessionCredentials(config) {
500
504
  if (!Number.isInteger(timeout) || timeout < 1 || timeout > 3600000) {
501
505
  throw new RangeError("refreshTimeoutMs must be between 1 and 3600000");
502
506
  }
507
+ const storageTimeout = config.storageTimeoutMs ?? 30000;
508
+ if (!Number.isInteger(storageTimeout) || storageTimeout < 1 || storageTimeout > 3600000) {
509
+ throw new RangeError("storageTimeoutMs must be between 1 and 3600000");
510
+ }
503
511
  let state;
512
+ const bounded = async (work, wrote) => {
513
+ const outcome = await withDeadline(work, storageTimeout);
514
+ if (outcome.settled)
515
+ return;
516
+ work.then(() => {
517
+ if (state?.credentials !== wrote)
518
+ return restore();
519
+ }).catch(() => {
520
+ return;
521
+ });
522
+ throw new Error("Session storage did not answer within storageTimeoutMs");
523
+ };
524
+ const restore = () => serialize(async () => {
525
+ const current = state;
526
+ if (current)
527
+ await bounded(config.storage.write(current.operation.context, current.credentials), current.credentials);
528
+ else
529
+ await bounded(config.storage.clear(), undefined);
530
+ });
504
531
  let flight;
505
532
  const read = (operation) => {
506
533
  if (!config.scope.owns(operation) || state?.operation !== operation)
@@ -509,7 +536,7 @@ function createSessionCredentials(config) {
509
536
  };
510
537
  const write = (operation, credentials) => serialize(async () => {
511
538
  read(operation);
512
- await config.storage.write(operation.context, credentials);
539
+ await bounded(config.storage.write(operation.context, credentials), credentials);
513
540
  operation.assertCurrent();
514
541
  });
515
542
  return {
@@ -526,7 +553,7 @@ function createSessionCredentials(config) {
526
553
  const generation = config.scope.generation;
527
554
  return serialize(async () => {
528
555
  if (config.scope.generation === generation)
529
- await config.storage.clear();
556
+ await bounded(config.storage.clear(), undefined);
530
557
  });
531
558
  },
532
559
  read,
@@ -547,9 +574,9 @@ function createSessionCredentials(config) {
547
574
  const result = operation.run(async () => {
548
575
  const next = await Promise.race([config.refresh(credentials, signal), abortPromise]);
549
576
  read(operation);
577
+ state = { operation, credentials: next };
550
578
  await write(operation, next);
551
579
  read(operation);
552
- state = { operation, credentials: next };
553
580
  return next;
554
581
  }).finally(() => {
555
582
  clearTimeout(timer);
@@ -299,33 +299,37 @@ function createWatchClient(contract, config) {
299
299
  }
300
300
  function handleFor(action, args) {
301
301
  const mine = new Set;
302
- let entry = entryFor({
302
+ const key = {
303
303
  service,
304
304
  action,
305
305
  digest: argumentsDigest(args),
306
306
  ...config.session && { instance: config.session.id }
307
- }, args);
307
+ };
308
+ let entry;
308
309
  return {
309
310
  subscribe(listeners) {
310
311
  config.session?.assertCurrent();
311
- if (entry.released)
312
- entry = entryFor(entry.key, args);
313
- if (entry.listeners.size >= maxListeners)
312
+ if (!entry || entry.released)
313
+ entry = entryFor(key, args);
314
+ const current = entry;
315
+ if (current.listeners.size >= maxListeners)
314
316
  throw new Error("Watch listener capacity exceeded");
315
317
  const registered = listeners;
316
318
  mine.add(registered);
317
- entry.listeners.add(registered);
318
- if (entry.hasValue)
319
- registered.value(entry.value);
320
- registered.state?.(entry.state);
321
- openWatch(config, openTimeoutMs, entry);
319
+ current.listeners.add(registered);
320
+ if (current.hasValue)
321
+ registered.value(current.value);
322
+ registered.state?.(current.state);
323
+ openWatch(config, openTimeoutMs, current);
322
324
  return () => {
323
325
  mine.delete(registered);
324
- entry.listeners.delete(registered);
325
- releaseWatchClientEntry(entry);
326
+ current.listeners.delete(registered);
327
+ releaseWatchClientEntry(current);
326
328
  };
327
329
  },
328
330
  close() {
331
+ if (!entry)
332
+ return;
329
333
  for (const listener of mine)
330
334
  entry.listeners.delete(listener);
331
335
  mine.clear();
@@ -8,7 +8,7 @@ import {
8
8
  createSocketIOServer,
9
9
  createUnixClientTransport,
10
10
  implementRegistry
11
- } from "../index-p3vsv1fb.js";
11
+ } from "../index-hadmp193.js";
12
12
  import"../index-y91ry9h2.js";
13
13
  import {
14
14
  ShutdownOptionsSchema,
@@ -19,14 +19,11 @@ import {
19
19
  sseRoute,
20
20
  streamingRoute,
21
21
  webSocketLane
22
- } from "../index-p3vsv1fb.js";
22
+ } from "../index-hadmp193.js";
23
23
  import"../index-y91ry9h2.js";
24
24
  import {
25
25
  createFileStateStore
26
26
  } from "../index-tm88avnj.js";
27
- import {
28
- withDeadline
29
- } from "../index-b9aefrqd.js";
30
27
  import {
31
28
  ShutdownOptionsSchema,
32
29
  ShutdownResultSchema,
@@ -36,6 +33,9 @@ import {
36
33
  } from "../index-3z73fh2c.js";
37
34
  import"../index-ndddxtyx.js";
38
35
  import"../index-dce5wxcq.js";
36
+ import {
37
+ withDeadline
38
+ } from "../index-b9aefrqd.js";
39
39
  import {
40
40
  inputIsQuery
41
41
  } from "../index-48ffdxgk.js";
@@ -7,7 +7,7 @@ import {
7
7
  createAgentRuntimeStore,
8
8
  isActiveRunState,
9
9
  replaceMessage
10
- } from "./index-0b1cnze2.js";
10
+ } from "./index-vqr7f7v8.js";
11
11
  import {
12
12
  AgentRecoverablePageSchema,
13
13
  AgentStoreEventEnvelopeSchema,
@@ -202,6 +202,9 @@ var memoryEvents = {
202
202
  items,
203
203
  ...selected.length > items.length && last ? { nextSeq: last.seq + 1 } : {}
204
204
  });
205
+ },
206
+ async find(transaction, input) {
207
+ return (transaction.events.get(input.conversationId) ?? []).find((event) => event.eventId === input.eventId);
205
208
  }
206
209
  };
207
210
  function scanMemoryRecoverable(runs, input) {
@@ -108,7 +108,11 @@ async function readProcessInstance(pid, procRoot = "/proc") {
108
108
  readFile2(`${procRoot}/sys/kernel/random/boot_id`, "utf8"),
109
109
  readlink(`${procRoot}/self/ns/pid`),
110
110
  readFile2(`${procRoot}/self/status`, "utf8"),
111
- readlink(`${procRoot}/self/ns/time`),
111
+ readlink(`${procRoot}/self/ns/time`).catch((error) => {
112
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
113
+ return "";
114
+ throw error;
115
+ }),
112
116
  readFile2(`${procRoot}/self/stat`, "utf8"),
113
117
  readFile2(`${procRoot}/${pid}/stat`, "utf8")
114
118
  ]);
@@ -10,7 +10,7 @@ import {
10
10
  AgentStoredRunSchema,
11
11
  appendEventIn,
12
12
  createAgentRuntimeStore
13
- } from "./index-0b1cnze2.js";
13
+ } from "./index-vqr7f7v8.js";
14
14
  import {
15
15
  AgentRecoverableDescriptorSchema,
16
16
  AgentRecoverablePageSchema,
@@ -528,6 +528,15 @@ var sqliteEvents = {
528
528
  items,
529
529
  ...hasMore && lastEvent ? { nextSeq: lastEvent.seq + 1 } : {}
530
530
  });
531
+ },
532
+ async find(transaction, input) {
533
+ const row = transaction.prepare(`
534
+ SELECT event_id, conversation_id, seq, schema_version, kind, occurred_at,
535
+ ignorable, payload
536
+ FROM stitchkit_agent_runtime_events
537
+ WHERE event_id = ? AND conversation_id = ?
538
+ `).get(input.eventId, input.conversationId);
539
+ return row === null || row === undefined ? undefined : parseEventRow(row);
531
540
  }
532
541
  };
533
542
  var sqliteArchive = {
@@ -714,7 +714,7 @@ var InvocationPayloadSchema = Base.extend({
714
714
 
715
715
  // src/agent-runtime/invocation-attempt.ts
716
716
  import { AsyncLocalStorage } from "node:async_hooks";
717
- import { randomUUID as randomUUID2 } from "node:crypto";
717
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
718
718
  import { wrapLanguageModel } from "ai";
719
719
 
720
720
  // src/agent-runtime/invocation-failure.ts
@@ -742,13 +742,18 @@ import {
742
742
  } from "node:crypto";
743
743
  import { z as z2 } from "zod";
744
744
  function invocationJson(value) {
745
+ const errors = new WeakSet;
745
746
  return z2.json().parse(JSON.parse(JSON.stringify(value, (_key, item) => {
746
747
  if (item instanceof Uint8Array)
747
748
  return { base64: Buffer.from(item).toString("base64") };
748
749
  if (item instanceof ArrayBuffer)
749
750
  return { base64: Buffer.from(item).toString("base64") };
750
- if (item instanceof Error)
751
+ if (item instanceof Error) {
752
+ if (errors.has(item))
753
+ return { name: item.name, message: item.message };
754
+ errors.add(item);
751
755
  return { name: item.name, message: item.message, cause: item.cause };
756
+ }
752
757
  return item;
753
758
  })));
754
759
  }
@@ -760,6 +765,7 @@ function createInvocationPayloads(store, key, maxBytes) {
760
765
  throw new TypeError("Invocation payload key must contain 32 bytes");
761
766
  const encryptionKey = Uint8Array.from(key);
762
767
  return {
768
+ maxBytes,
763
769
  async write(identity, value) {
764
770
  const data = Buffer.from(serializeCanonicalJson(invocationJson(value)));
765
771
  if (data.byteLength > maxBytes)
@@ -771,7 +777,9 @@ function createInvocationPayloads(store, key, maxBytes) {
771
777
  cipher.setAAD(Buffer.from(JSON.stringify([identity.conversationId, identity.invocationId, artifactId])));
772
778
  const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]);
773
779
  const { conversationId } = identity;
774
- await store.appendEvent({
780
+ if (!store.appendEventOnce)
781
+ throw new TypeError("Invocation payloads require atomic store.appendEventOnce");
782
+ await store.appendEventOnce({
775
783
  conversationId,
776
784
  kind: "provider/payload",
777
785
  payload: {
@@ -783,7 +791,7 @@ function createInvocationPayloads(store, key, maxBytes) {
783
791
  tag: cipher.getAuthTag().toString("base64"),
784
792
  ciphertext: ciphertext.toString("base64")
785
793
  }
786
- });
794
+ }, artifactId);
787
795
  return { artifactId, sha256 };
788
796
  },
789
797
  decrypt(conversationId, value) {
@@ -1067,8 +1075,11 @@ function wrapInvocationModel(writer, selected, step, model = selected.model) {
1067
1075
  }
1068
1076
  return context;
1069
1077
  });
1070
- const finish = async (context, status, payload, usage, response, providerMetadata) => invocationAudit(async () => {
1071
- const artifact = await writer.payloads.write(writer.identity, payload);
1078
+ const finish = (...args) => recordResponse(...args).catch((error) => {
1079
+ writer.receiptErrors.push(error);
1080
+ });
1081
+ const recordResponse = async (context, status, payload, usage, response, providerMetadata) => invocationAudit(async () => {
1082
+ const artifact = await writer.payloads.write(writer.identity, boundedEvidence(payload, Math.floor(writer.payloads.maxBytes / 2)));
1072
1083
  await writer.append({
1073
1084
  ...writer.identity,
1074
1085
  type: "provider/response",
@@ -1126,13 +1137,12 @@ function wrapInvocationModel(writer, selected, step, model = selected.model) {
1126
1137
  let ended = false;
1127
1138
  let failed = false;
1128
1139
  let response;
1129
- const output = [];
1130
- let outputBytes = 0;
1140
+ const evidence = streamEvidence(Math.floor(writer.payloads.maxBytes / 2));
1131
1141
  const end = async (status, value, usage, metadata) => {
1132
1142
  if (ended)
1133
1143
  return;
1134
1144
  ended = true;
1135
- await finish(context, status, { output, terminal: value }, usage, response, metadata);
1145
+ await finish(context, status, evidence.payload(value), usage, response, metadata);
1136
1146
  };
1137
1147
  return {
1138
1148
  ...result,
@@ -1159,14 +1169,7 @@ function wrapInvocationModel(writer, selected, step, model = selected.model) {
1159
1169
  } else {
1160
1170
  if (part.type === "error")
1161
1171
  failed = true;
1162
- const json = invocationJson(part);
1163
- outputBytes += Buffer.byteLength(JSON.stringify(json));
1164
- if (outputBytes > 1048576) {
1165
- await reader.cancel();
1166
- await end("failed", { reason: "receipt-output-limit" });
1167
- throw new RangeError("Invocation stream evidence exceeds 1 MiB");
1168
- }
1169
- output.push(json);
1172
+ evidence.record(part);
1170
1173
  }
1171
1174
  controller.enqueue(part);
1172
1175
  },
@@ -1183,6 +1186,45 @@ function wrapInvocationModel(writer, selected, step, model = selected.model) {
1183
1186
  }
1184
1187
  });
1185
1188
  }
1189
+ function boundedEvidence(payload, limit) {
1190
+ const serialized = JSON.stringify(invocationJson(payload));
1191
+ const bytes = Buffer.byteLength(serialized);
1192
+ if (bytes <= limit)
1193
+ return payload;
1194
+ return {
1195
+ truncated: { bytes, sha256: createHash2("sha256").update(serialized).digest("hex") }
1196
+ };
1197
+ }
1198
+ function streamEvidence(limit) {
1199
+ const output = [];
1200
+ const whole = createHash2("sha256");
1201
+ let bytes = 0;
1202
+ let parts = 0;
1203
+ let truncated = false;
1204
+ return {
1205
+ record(part) {
1206
+ const json = invocationJson(part);
1207
+ const serialized = JSON.stringify(json);
1208
+ whole.update(serialized).update(`
1209
+ `);
1210
+ parts += 1;
1211
+ const size = Buffer.byteLength(serialized);
1212
+ if (!truncated && bytes + size > limit)
1213
+ truncated = true;
1214
+ if (truncated)
1215
+ return;
1216
+ bytes += size;
1217
+ output.push(json);
1218
+ },
1219
+ payload(terminal) {
1220
+ return {
1221
+ output,
1222
+ terminal,
1223
+ ...truncated && { truncated: { parts, sha256: whole.digest("hex") } }
1224
+ };
1225
+ }
1226
+ };
1227
+ }
1186
1228
  function reportedUsage(selected, value, providerMetadata) {
1187
1229
  const usage = {
1188
1230
  inputTokens: value?.inputTokens.total,
@@ -1240,6 +1282,13 @@ async function readInvocationRecords(store, input) {
1240
1282
  };
1241
1283
  }
1242
1284
  async function readInvocationPayload(store, payloads, input) {
1285
+ const keyed = await store.findEventOnce?.({
1286
+ conversationId: input.conversationId,
1287
+ kind: "provider/payload",
1288
+ key: input.artifactId
1289
+ });
1290
+ if (keyed)
1291
+ return payloads.decrypt(input.conversationId, keyed.payload);
1243
1292
  let fromSeq;
1244
1293
  do {
1245
1294
  const page = await store.readEvents({
@@ -1268,6 +1317,30 @@ function beginAgentInvocation(ledger, store, input, context) {
1268
1317
  throw new TypeError("Agent and invocations must share the same store");
1269
1318
  return binding.start(input, context);
1270
1319
  }
1320
+ function withReceipt(result, writer) {
1321
+ const [receiptError] = writer.receiptErrors;
1322
+ return receiptError === undefined ? result : { ...result, receiptError };
1323
+ }
1324
+ async function generateWithFallback(models, writer, input, abortSignal) {
1325
+ for (const [step, key] of input.models.entries()) {
1326
+ abortSignal.throwIfAborted();
1327
+ const selected = models.resolve(key);
1328
+ try {
1329
+ const result = await generateText({
1330
+ model: wrapInvocationModel(writer, selected, step),
1331
+ prompt: input.prompt,
1332
+ ...input.settings,
1333
+ maxRetries: 0,
1334
+ abortSignal
1335
+ });
1336
+ return result.text;
1337
+ } catch (error) {
1338
+ if (!hasProviderOrigin(error) || abortSignal.aborted || step === input.models.length - 1)
1339
+ throw error;
1340
+ }
1341
+ }
1342
+ throw new Error("No completion model attempt available");
1343
+ }
1271
1344
  function createModelInvocationLedger(config) {
1272
1345
  const { store } = config;
1273
1346
  const appendOnce = store.appendEventOnce?.bind(store);
@@ -1315,6 +1388,7 @@ function createModelInvocationLedger(config) {
1315
1388
  process: executingProcess,
1316
1389
  payloads,
1317
1390
  onAttempt: config.onAttempt,
1391
+ receiptErrors: [],
1318
1392
  async append(record) {
1319
1393
  const parsed = ModelInvocationRecordSchema.parse(record);
1320
1394
  await store.appendEvent({
@@ -1349,35 +1423,31 @@ function createModelInvocationLedger(config) {
1349
1423
  return { invocationId, outcome: "duplicate" };
1350
1424
  const deadline = AbortSignal.timeout(input.timeoutMs);
1351
1425
  const abortSignal = signal ? AbortSignal.any([signal, deadline]) : deadline;
1426
+ let text;
1352
1427
  try {
1353
- for (const [step, key] of input.models.entries()) {
1354
- abortSignal.throwIfAborted();
1355
- const selected = config.models.resolve(key);
1356
- try {
1357
- const result = await generateText({
1358
- model: wrapInvocationModel(writer, selected, step),
1359
- prompt: input.prompt,
1360
- ...input.settings,
1361
- maxRetries: 0,
1362
- abortSignal
1363
- });
1364
- await finish(writer, "succeeded");
1365
- return { invocationId, outcome: "succeeded", text: result.text };
1366
- } catch (error) {
1367
- if (!hasProviderOrigin(error) || abortSignal.aborted || step === input.models.length - 1)
1368
- throw error;
1369
- }
1370
- }
1371
- throw new Error("No completion model attempt available");
1428
+ text = await generateWithFallback(config.models, writer, input, abortSignal);
1372
1429
  } catch (error) {
1373
1430
  const status = abortSignal.aborted ? "cancelled" : "failed";
1374
- const artifact = await payloads.write(writer.identity, { error });
1375
- await finish(writer, status, {
1376
- artifactId: artifact.artifactId,
1377
- payloadSha256: artifact.sha256
1378
- });
1379
- return { invocationId, outcome: status };
1431
+ try {
1432
+ const artifact = await payloads.write(writer.identity, { error });
1433
+ await finish(writer, status, {
1434
+ artifactId: artifact.artifactId,
1435
+ payloadSha256: artifact.sha256
1436
+ });
1437
+ } catch (receiptError) {
1438
+ writer.receiptErrors.push(receiptError);
1439
+ await finish(writer, status).catch((error2) => {
1440
+ writer.receiptErrors.push(error2);
1441
+ });
1442
+ }
1443
+ return withReceipt({ invocationId, outcome: status }, writer);
1444
+ }
1445
+ try {
1446
+ await finish(writer, "succeeded");
1447
+ } catch (receiptError) {
1448
+ writer.receiptErrors.push(receiptError);
1380
1449
  }
1450
+ return withReceipt({ invocationId, outcome: "succeeded", text }, writer);
1381
1451
  },
1382
1452
  async read(rawInput, context) {
1383
1453
  const input = InvocationReadInputSchema.parse(rawInput);
@@ -2751,14 +2821,14 @@ async function* ownedProviderStream(input) {
2751
2821
  }
2752
2822
 
2753
2823
  // src/agent-runtime/run-provider-ledger.ts
2754
- import { createHash as createHash2 } from "node:crypto";
2824
+ import { createHash as createHash3 } from "node:crypto";
2755
2825
  function withoutBinaryParts(message) {
2756
2826
  const reference = (value) => {
2757
2827
  if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
2758
2828
  const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
2759
2829
  return {
2760
2830
  binary: true,
2761
- sha256: createHash2("sha256").update(bytes).digest("hex"),
2831
+ sha256: createHash3("sha256").update(bytes).digest("hex"),
2762
2832
  bytes: bytes.byteLength
2763
2833
  };
2764
2834
  }
@@ -2813,7 +2883,7 @@ async function createRunProviderLedger(input) {
2813
2883
  const messageShas = [];
2814
2884
  for (const message of request.messages) {
2815
2885
  const body = canonicalAgentJson(JSON.parse(JSON.stringify(withoutBinaryParts(message))));
2816
- const sha256 = createHash2("sha256").update(body).digest("hex");
2886
+ const sha256 = createHash3("sha256").update(body).digest("hex");
2817
2887
  messageShas.push(sha256);
2818
2888
  if (knownMessageShas.has(sha256))
2819
2889
  continue;
@@ -2824,8 +2894,8 @@ async function createRunProviderLedger(input) {
2824
2894
  });
2825
2895
  knownMessageShas.add(sha256);
2826
2896
  }
2827
- const instructionsSha256 = createHash2("sha256").update(typeof request.instructions === "string" ? request.instructions : canonicalAgentJson(JSON.parse(JSON.stringify(withoutBinaryParts(request.instructions))))).digest("hex");
2828
- const bodySha256 = createHash2("sha256").update(instructionsSha256).update(messageShas.join(",")).digest("hex");
2897
+ const instructionsSha256 = createHash3("sha256").update(typeof request.instructions === "string" ? request.instructions : canonicalAgentJson(JSON.parse(JSON.stringify(withoutBinaryParts(request.instructions))))).digest("hex");
2898
+ const bodySha256 = createHash3("sha256").update(instructionsSha256).update(messageShas.join(",")).digest("hex");
2829
2899
  await store.appendEvent({
2830
2900
  conversationId: state.run.conversationId,
2831
2901
  kind: "provider/request",
@@ -3596,7 +3666,7 @@ async function commitRunTerminal(execution) {
3596
3666
  }
3597
3667
 
3598
3668
  // src/agent-runtime/run-prompt-history.ts
3599
- import { createHash as createHash3 } from "node:crypto";
3669
+ import { createHash as createHash4 } from "node:crypto";
3600
3670
  function snapshotForRunPrompt(snapshot, runId) {
3601
3671
  const runIndex = snapshot.runs.findIndex((candidate) => candidate.id === runId);
3602
3672
  if (runIndex < 0)
@@ -3615,7 +3685,7 @@ var USER_INSTRUCTION_SEED_KEY = "prompt.user-instructions";
3615
3685
  function seededInstructionMessage(input) {
3616
3686
  return AgentMessageSchema.parse({
3617
3687
  schemaVersion: 1,
3618
- id: createHash3("sha256").update(`${input.conversationId}\x00${input.seedKey}\x00${input.index}`).digest("hex"),
3688
+ id: createHash4("sha256").update(`${input.conversationId}\x00${input.seedKey}\x00${input.index}`).digest("hex"),
3619
3689
  conversationId: input.conversationId,
3620
3690
  role: "user",
3621
3691
  status: "committed",
@@ -2365,11 +2365,20 @@ function bindRealtimeServer(contract, handle, options = {}) {
2365
2365
  onRejected: options.onRejected,
2366
2366
  logger: options.logger
2367
2367
  });
2368
- handler({
2369
- raw,
2370
- events,
2371
- to: (room) => outbound(raw.to(room))
2372
- });
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
+ }
2373
2382
  };
2374
2383
  handle.io.on("connection", listener);
2375
2384
  return () => {
@@ -429,27 +429,38 @@ async function importConversation(driver, bytes) {
429
429
 
430
430
  // src/agent-runtime/store-event-once.ts
431
431
  import { createHash } from "node:crypto";
432
- function appendEventOnce(driver, input, key) {
433
- const parsed = AppendAgentStoreEventSchema.parse(input);
432
+ function eventOnceId(conversationId, kind, key) {
434
433
  if (!key)
435
434
  throw new TypeError("Event admission key must not be empty");
436
- const eventId = createHash("sha256").update(JSON.stringify([parsed.conversationId, parsed.kind, key])).digest("hex");
435
+ return createHash("sha256").update(JSON.stringify([conversationId, kind, key])).digest("hex");
436
+ }
437
+ async function findEvent(driver, transaction, conversationId, eventId) {
438
+ if (driver.events.find)
439
+ return driver.events.find(transaction, { conversationId, eventId });
440
+ let fromSeq;
441
+ do {
442
+ const page = await driver.events.list(transaction, {
443
+ conversationId,
444
+ fromSeq,
445
+ limit: 1e4
446
+ });
447
+ const found = page.items.find((event) => event.eventId === eventId);
448
+ if (found)
449
+ return found;
450
+ fromSeq = page.nextSeq;
451
+ } while (fromSeq !== undefined);
452
+ return;
453
+ }
454
+ function appendEventOnce(driver, input, key) {
455
+ const parsed = AppendAgentStoreEventSchema.parse(input);
456
+ const eventId = eventOnceId(parsed.conversationId, parsed.kind, key);
437
457
  return driver.transaction(async (transaction) => {
438
458
  if (await driver.conversations?.isPurged(transaction, parsed.conversationId)) {
439
459
  throw new AgentConversationPurgedError;
440
460
  }
441
- let fromSeq;
442
- do {
443
- const page = await driver.events.list(transaction, {
444
- conversationId: parsed.conversationId,
445
- fromSeq,
446
- limit: 1e4
447
- });
448
- const previous = page.items.find((event2) => event2.eventId === eventId);
449
- if (previous)
450
- return { outcome: "duplicate", event: previous };
451
- fromSeq = page.nextSeq;
452
- } while (fromSeq !== undefined);
461
+ const previous = await findEvent(driver, transaction, parsed.conversationId, eventId);
462
+ if (previous)
463
+ return { outcome: "duplicate", event: previous };
453
464
  const event = await driver.events.append(transaction, {
454
465
  ...agentStoreEventDraft(parsed),
455
466
  eventId
@@ -457,6 +468,10 @@ function appendEventOnce(driver, input, key) {
457
468
  return { outcome: "applied", event };
458
469
  });
459
470
  }
471
+ function findEventOnce(driver, input) {
472
+ const eventId = eventOnceId(input.conversationId, input.kind, input.key);
473
+ return driver.transaction((transaction) => findEvent(driver, transaction, input.conversationId, eventId), { access: "read" });
474
+ }
460
475
 
461
476
  // src/agent-runtime/store-mutate.ts
462
477
  import { z as z4 } from "zod";
@@ -1011,6 +1026,7 @@ function createAgentRuntimeStore(driver) {
1011
1026
  loadRun: (input) => loadRun(driver, input),
1012
1027
  listActiveRuns: (conversationId) => listActiveRuns(driver, conversationId),
1013
1028
  appendEventOnce: (input, key) => appendEventOnce(driver, input, key),
1029
+ findEventOnce: (input) => findEventOnce(driver, input),
1014
1030
  appendEvent: (input) => appendEvent(driver, input),
1015
1031
  readEvents: (input) => readEvents(driver, input),
1016
1032
  exportConversation: (conversationId) => exportConversation(driver, conversationId),