stitchkit 0.59.4 → 0.60.1

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 (110) hide show
  1. package/README.md +7 -3
  2. package/dist/agent-runtime/admission-lanes.d.ts +35 -0
  3. package/dist/agent-runtime/admission-lanes.d.ts.map +1 -0
  4. package/dist/agent-runtime/coordinator.d.ts +46 -2
  5. package/dist/agent-runtime/coordinator.d.ts.map +1 -1
  6. package/dist/agent-runtime/events.d.ts +7 -0
  7. package/dist/agent-runtime/events.d.ts.map +1 -1
  8. package/dist/agent-runtime/history.d.ts +7 -0
  9. package/dist/agent-runtime/history.d.ts.map +1 -1
  10. package/dist/agent-runtime/models.d.ts +2 -3
  11. package/dist/agent-runtime/models.d.ts.map +1 -1
  12. package/dist/agent-runtime/observability.d.ts.map +1 -1
  13. package/dist/agent-runtime/run-execution.d.ts +31 -0
  14. package/dist/agent-runtime/run-execution.d.ts.map +1 -0
  15. package/dist/agent-runtime/runtime-internals.d.ts +25 -0
  16. package/dist/agent-runtime/runtime-internals.d.ts.map +1 -0
  17. package/dist/agent-runtime/runtime-result.d.ts +11 -0
  18. package/dist/agent-runtime/runtime-result.d.ts.map +1 -0
  19. package/dist/agent-runtime/runtime.d.ts +16 -15
  20. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  21. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  22. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  23. package/dist/agent-runtime/store.d.ts +9 -2
  24. package/dist/agent-runtime/store.d.ts.map +1 -1
  25. package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
  26. package/dist/agent-runtime/terminal-status.d.ts +13 -0
  27. package/dist/agent-runtime/terminal-status.d.ts.map +1 -0
  28. package/dist/agent-runtime.d.ts +5 -3
  29. package/dist/agent-runtime.d.ts.map +1 -1
  30. package/dist/agent-runtime.js +303 -163
  31. package/dist/application/activity.d.ts +7 -2
  32. package/dist/application/activity.d.ts.map +1 -1
  33. package/dist/application/grammy.d.ts +14 -2
  34. package/dist/application/grammy.d.ts.map +1 -1
  35. package/dist/application/health.d.ts +9 -2
  36. package/dist/application/health.d.ts.map +1 -1
  37. package/dist/application/kernel.d.ts +61 -5
  38. package/dist/application/kernel.d.ts.map +1 -1
  39. package/dist/application/schemas.d.ts +42 -0
  40. package/dist/application/schemas.d.ts.map +1 -1
  41. package/dist/application-grammy.js +5 -3
  42. package/dist/application-opentelemetry.js +2 -2
  43. package/dist/application.d.ts +3 -3
  44. package/dist/application.d.ts.map +1 -1
  45. package/dist/application.js +17 -8
  46. package/dist/cli.js +7 -6
  47. package/dist/contract/errors.d.ts +7 -0
  48. package/dist/contract/errors.d.ts.map +1 -1
  49. package/dist/contract/index.js +1 -1
  50. package/dist/declaration.d.ts +345 -0
  51. package/dist/declaration.d.ts.map +1 -0
  52. package/dist/declaration.js +183 -0
  53. package/dist/{index-3nwng7hs.js → index-1pgeyyee.js} +4 -4
  54. package/dist/{index-s8w2y7kr.js → index-2cgbdckv.js} +26 -18
  55. package/dist/{index-gnvzyw0k.js → index-6taryy00.js} +2 -2
  56. package/dist/{index-5c8n2123.js → index-82e74yfx.js} +69 -33
  57. package/dist/{index-mgf6cdkm.js → index-8eywc9zv.js} +35 -1
  58. package/dist/{index-0nc0cddp.js → index-a59da114.js} +17 -28
  59. package/dist/{index-f6pymtqe.js → index-mfw1pec7.js} +7 -5
  60. package/dist/{index-y2rb7dwx.js → index-pzyt11ch.js} +5 -1
  61. package/dist/{index-mr617n62.js → index-svqhcrrj.js} +1 -1
  62. package/dist/{index-28cqssm3.js → index-t8xrqc9g.js} +7 -0
  63. package/dist/{index-7c0gkyvj.js → index-tvwcrx2d.js} +5 -3
  64. package/dist/{index-1tkyng3g.js → index-vkk06pv1.js} +1 -9
  65. package/dist/{index-ps5pxp3h.js → index-wd8g8z6e.js} +2 -79
  66. package/dist/{index-sa2mbwa7.js → index-wrhf06ak.js} +7 -5
  67. package/dist/{index-2nzpcfbp.js → index-x8v8b7rc.js} +2 -2
  68. package/dist/index-xxye8j3k.js +85 -0
  69. package/dist/{index-41wm56v0.js → index-y91zd0ch.js} +1 -1
  70. package/dist/index.js +1 -1
  71. package/dist/internal/observability-sink.d.ts.map +1 -1
  72. package/dist/internal/route-pattern.d.ts +9 -0
  73. package/dist/internal/route-pattern.d.ts.map +1 -1
  74. package/dist/node.d.ts +1 -1
  75. package/dist/node.d.ts.map +1 -1
  76. package/dist/node.js +8 -7
  77. package/dist/observability/index.js +6 -5
  78. package/dist/observability/sanitize.d.ts +3 -1
  79. package/dist/observability/sanitize.d.ts.map +1 -1
  80. package/dist/remote.js +6 -4
  81. package/dist/server/error-hook.d.ts +10 -3
  82. package/dist/server/error-hook.d.ts.map +1 -1
  83. package/dist/server/index.d.ts +1 -1
  84. package/dist/server/index.d.ts.map +1 -1
  85. package/dist/server/index.js +16 -18
  86. package/dist/server/middleware/cors.d.ts +5 -2
  87. package/dist/server/middleware/cors.d.ts.map +1 -1
  88. package/dist/server/openapi.d.ts.map +1 -1
  89. package/dist/server/router.d.ts.map +1 -1
  90. package/dist/server/socket-io-config.d.ts +68 -3
  91. package/dist/server/socket-io-config.d.ts.map +1 -1
  92. package/dist/server/socket-io-node.d.ts +1 -1
  93. package/dist/server/socket-io-node.d.ts.map +1 -1
  94. package/dist/server/socket-io.d.ts +1 -1
  95. package/dist/server/socket-io.d.ts.map +1 -1
  96. package/dist/testing/surface-manifest.d.ts.map +1 -1
  97. package/dist/testing.js +9 -10
  98. package/dist/tools/flatten-join.d.ts +2 -0
  99. package/dist/tools/flatten-join.d.ts.map +1 -1
  100. package/dist/tools/flatten.d.ts.map +1 -1
  101. package/dist/tools/mcp-prepare.d.ts +0 -2
  102. package/dist/tools/mcp-prepare.d.ts.map +1 -1
  103. package/dist/tools/runtime-tool.d.ts +0 -3
  104. package/dist/tools/runtime-tool.d.ts.map +1 -1
  105. package/dist/tools/schema.d.ts +0 -4
  106. package/dist/tools/schema.d.ts.map +1 -1
  107. package/dist/tools.js +13 -11
  108. package/llms-full.txt +926 -68
  109. package/llms.txt +1 -0
  110. package/package.json +10 -6
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ToolExecutionControlError,
3
3
  isToolExecutionControlError
4
- } from "./index-sa2mbwa7.js";
4
+ } from "./index-wrhf06ak.js";
5
5
  import {
6
6
  AgentAssistantPlaceholderSchema,
7
7
  AgentControlPartSchema,
@@ -34,15 +34,16 @@ import"./index-6djpbnda.js";
34
34
  import"./index-cby4ar3v.js";
35
35
  import {
36
36
  createBoundedSinkManager
37
- } from "./index-0nc0cddp.js";
38
- import"./index-7c0gkyvj.js";
39
- import"./index-ps5pxp3h.js";
37
+ } from "./index-a59da114.js";
38
+ import"./index-tvwcrx2d.js";
39
+ import"./index-wd8g8z6e.js";
40
40
  import"./index-6k1937bx.js";
41
41
  import {
42
42
  childSpan,
43
43
  createTraceContext
44
- } from "./index-y2rb7dwx.js";
44
+ } from "./index-pzyt11ch.js";
45
45
  import"./index-smpbdg6k.js";
46
+ import"./index-xxye8j3k.js";
46
47
 
47
48
  // src/agent-runtime/compaction.ts
48
49
  function providerValidTurn(messages) {
@@ -148,6 +149,13 @@ function structuredCompaction(config) {
148
149
  };
149
150
  }
150
151
  // src/agent-runtime/coordinator.ts
152
+ function assertCloseBudgets(options) {
153
+ for (const [name, value] of Object.entries(options)) {
154
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
155
+ throw new TypeError(`${name} must be a non-negative safe integer`);
156
+ }
157
+ }
158
+ }
151
159
  function createAgentSessionCoordinator() {
152
160
  const lanes = new Map;
153
161
  let closed = false;
@@ -230,11 +238,7 @@ function createAgentSessionCoordinator() {
230
238
  return true;
231
239
  },
232
240
  async close(options = {}) {
233
- for (const [name, value] of Object.entries(options)) {
234
- if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) {
235
- throw new TypeError(`${name} must be a non-negative safe integer`);
236
- }
237
- }
241
+ assertCloseBudgets(options);
238
242
  closed = true;
239
243
  const error = new Error("Agent session coordinator is closed");
240
244
  for (const lane of lanes.values()) {
@@ -243,21 +247,31 @@ function createAgentSessionCoordinator() {
243
247
  }
244
248
  const active = [...lanes.values()].map((lane) => lane.active).filter((run) => run !== undefined);
245
249
  const settlements = active.map((run) => run.settled);
250
+ const outstanding = new Set(settlements);
251
+ for (const settled of settlements) {
252
+ settled.then(() => {
253
+ outstanding.delete(settled);
254
+ });
255
+ }
246
256
  const drain = Promise.all(settlements).then(() => {
247
257
  return;
248
258
  });
249
- if (options.drainTimeoutMs !== undefined) {
250
- const drained = await waitWithin(drain, options.drainTimeoutMs);
251
- if (drained)
252
- return;
259
+ const finished = () => ({
260
+ settled: outstanding.size === 0,
261
+ timedOut: outstanding.size > 0,
262
+ remaining: outstanding.size
263
+ });
264
+ if (options.gracePeriodMs !== undefined && await waitWithin(drain, options.gracePeriodMs)) {
265
+ return finished();
253
266
  }
254
267
  for (const run of active)
255
268
  run.controller.abort("shutdown");
256
- if (options.drainTimeoutMs === undefined)
257
- return drain;
258
- if (options.forceTimeoutMs === undefined)
259
- return;
269
+ if (options.forceTimeoutMs === undefined) {
270
+ await drain;
271
+ return finished();
272
+ }
260
273
  await waitWithin(drain, options.forceTimeoutMs);
274
+ return finished();
261
275
  },
262
276
  isRunning(key) {
263
277
  return lanes.get(key)?.active !== undefined;
@@ -431,12 +445,16 @@ async function userMessage(message, options) {
431
445
  });
432
446
  continue;
433
447
  }
434
- const fallback = options.unresolvedFile ?? "text";
448
+ const fallback = options.unresolvedFile ?? "omit";
435
449
  if (fallback === "error") {
436
450
  throw new Error(`No history file resolver configured for ${part.reference}`);
437
451
  }
438
452
  if (fallback === "text") {
439
- content.push({ type: "text", text: `[attachment: ${part.reference}]` });
453
+ const described = part.filename ?? part.mediaType;
454
+ content.push({
455
+ type: "text",
456
+ text: described ? `[attachment: ${described}]` : "[attachment]"
457
+ });
440
458
  }
441
459
  }
442
460
  if (content.length === 0)
@@ -693,7 +711,18 @@ function createAgentObservability(config) {
693
711
  ...config.onSinkError && { onSinkError: config.onSinkError },
694
712
  ...config.onDrop && { onDrop: config.onDrop }
695
713
  });
714
+ const DEDUPLICATION_WINDOW = 1e4;
696
715
  const emitted = new Set;
716
+ const ring = new Array(DEDUPLICATION_WINDOW).fill(undefined);
717
+ let cursor = 0;
718
+ const remember = (eventId) => {
719
+ emitted.add(eventId);
720
+ const evicted = ring[cursor];
721
+ ring[cursor] = eventId;
722
+ cursor = (cursor + 1) % DEDUPLICATION_WINDOW;
723
+ if (evicted !== undefined)
724
+ emitted.delete(evicted);
725
+ };
697
726
  return {
698
727
  rootTrace(parent) {
699
728
  const trace = parent ? childSpan(parent) : createTraceContext();
@@ -703,7 +732,7 @@ function createAgentObservability(config) {
703
732
  const parsed = AgentRunEventSchema.parse(rawEvent);
704
733
  if ((config.deduplicate ?? true) && emitted.has(parsed.eventId))
705
734
  return;
706
- emitted.add(parsed.eventId);
735
+ remember(parsed.eventId);
707
736
  manager.submit(() => config.includeInternalCause ? parsed : AgentRunEventSchema.omit({ internalCause: true }).parse(parsed));
708
737
  },
709
738
  flush: () => manager.flush(),
@@ -893,13 +922,74 @@ function defineAgentProtocol(config) {
893
922
  parsePart: (input) => AgentMessageSchema.shape.parts.element.parse(input)
894
923
  };
895
924
  }
896
- // src/agent-runtime/runtime.ts
897
- import {
898
- stepCountIs,
899
- streamText
900
- } from "ai";
925
+ // src/agent-runtime/admission-lanes.ts
926
+ function createRuntimeAdmissionLanes() {
927
+ const lanes = new Map;
928
+ const admit = (runId) => {
929
+ const admission = { runId, completion: Promise.withResolvers() };
930
+ admission.completion.promise.catch(() => {
931
+ return;
932
+ });
933
+ return admission;
934
+ };
935
+ return {
936
+ reserve(key, runId) {
937
+ const existing = lanes.get(key);
938
+ const lane = existing ?? { acceptanceTail: Promise.resolve() };
939
+ if (!existing)
940
+ lanes.set(key, lane);
941
+ if (!lane.head) {
942
+ lane.head = admit(runId);
943
+ return { lane, admission: lane.head, shouldSchedule: true };
944
+ }
945
+ if (!lane.pending) {
946
+ lane.pending = admit(runId);
947
+ return { lane, admission: lane.pending, shouldSchedule: true };
948
+ }
949
+ return { lane, admission: lane.pending, shouldSchedule: false };
950
+ },
951
+ settle(key, admission) {
952
+ const lane = lanes.get(key);
953
+ if (!lane)
954
+ return;
955
+ if (lane.head?.runId === admission.runId) {
956
+ lane.head = lane.pending;
957
+ lane.pending = undefined;
958
+ } else if (lane.pending?.runId === admission.runId) {
959
+ lane.pending = undefined;
960
+ }
961
+ if (!lane.head && !lane.pending)
962
+ lanes.delete(key);
963
+ },
964
+ async waitForAcceptances(lane) {
965
+ while (true) {
966
+ const tail = lane.acceptanceTail;
967
+ await tail.catch(() => {
968
+ return;
969
+ });
970
+ if (tail === lane.acceptanceTail)
971
+ return;
972
+ }
973
+ }
974
+ };
975
+ }
976
+
977
+ // src/agent-runtime/run-execution.ts
978
+ import { stepCountIs, streamText } from "ai";
979
+
980
+ // src/agent-runtime/runtime-internals.ts
901
981
  import { z as z5 } from "zod";
902
982
 
983
+ // src/agent-runtime/terminal-status.ts
984
+ function assistantStatus(reason) {
985
+ if (reason === "success" || reason === "policy_stop")
986
+ return "completed";
987
+ if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
988
+ return "interrupted";
989
+ }
990
+ return "failed";
991
+ }
992
+
903
993
  // src/agent-runtime/terminal-commit.ts
904
994
  class AgentRuntimeConflictError extends Error {
905
995
  constructor(operation) {
@@ -912,20 +1002,12 @@ function appliedSnapshot(result, operation) {
912
1002
  return result.snapshot;
913
1003
  throw new AgentRuntimeConflictError(operation);
914
1004
  }
915
- function terminalMessageStatus(reason) {
916
- if (reason === "success" || reason === "policy_stop")
917
- return "completed";
918
- if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
919
- return "interrupted";
920
- }
921
- return "failed";
922
- }
923
1005
  function canonicalTerminal(snapshot, runId, retainedAssistant) {
924
1006
  const run = snapshot.runs.find((candidate) => candidate.id === runId);
925
1007
  if (!run?.terminalReason)
926
1008
  return;
927
1009
  const assistant = snapshot.messages.find((message) => message.id === run.assistantMessageId) ?? retainedAssistant;
928
- if (snapshot.conversationId !== run.conversationId || !assistant || assistant.id !== run.assistantMessageId || assistant.conversationId !== run.conversationId || assistant.runId !== run.id || assistant.role !== "assistant" || assistant.status !== terminalMessageStatus(run.terminalReason)) {
1010
+ if (snapshot.conversationId !== run.conversationId || !assistant || assistant.id !== run.assistantMessageId || assistant.conversationId !== run.conversationId || assistant.runId !== run.id || assistant.role !== "assistant" || assistant.status !== assistantStatus(run.terminalReason)) {
929
1011
  throw new AgentRuntimeConflictError("terminal result projection");
930
1012
  }
931
1013
  return {
@@ -999,7 +1081,7 @@ async function commitAgentRunTerminal(input) {
999
1081
  }
1000
1082
  }
1001
1083
 
1002
- // src/agent-runtime/runtime.ts
1084
+ // src/agent-runtime/runtime-internals.ts
1003
1085
  function findRun(runs, runId) {
1004
1086
  const run = runs.find((candidate) => candidate.id === runId);
1005
1087
  if (!run)
@@ -1025,14 +1107,6 @@ function appendText(parts, text) {
1025
1107
  }
1026
1108
  parts.push(AgentMessagePartSchema.parse({ type: "text", text }));
1027
1109
  }
1028
- function assistantStatus(reason) {
1029
- if (reason === "success" || reason === "policy_stop")
1030
- return "completed";
1031
- if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
1032
- return "interrupted";
1033
- }
1034
- return "failed";
1035
- }
1036
1110
  function abortTerminalReason(signal) {
1037
1111
  if (signal.reason === "shutdown")
1038
1112
  return "shutdown";
@@ -1074,88 +1148,20 @@ function createIdleDeadline(parent, timeoutMs) {
1074
1148
  }
1075
1149
  };
1076
1150
  }
1077
- function createAgentRuntime(config) {
1078
- const coordinator = createAgentSessionCoordinator();
1079
- const tickets = new Map;
1080
- const generateId = config.generateId ?? (() => crypto.randomUUID());
1081
- const now = config.now ?? (() => new Date);
1082
- const runtimeEpoch = generateId();
1083
- const admissionLanes = new Map;
1084
- const reserveAdmission = (key, runId) => {
1085
- const existing = admissionLanes.get(key);
1086
- const lane = existing ?? { acceptanceTail: Promise.resolve() };
1087
- if (!existing)
1088
- admissionLanes.set(key, lane);
1089
- if (!lane.head) {
1090
- const admission = { runId, completion: Promise.withResolvers() };
1091
- admission.completion.promise.catch(() => {
1092
- return;
1093
- });
1094
- lane.head = admission;
1095
- return { lane, admission, shouldSchedule: true };
1096
- }
1097
- if (!lane.pending) {
1098
- const admission = { runId, completion: Promise.withResolvers() };
1099
- admission.completion.promise.catch(() => {
1100
- return;
1101
- });
1102
- lane.pending = admission;
1103
- return { lane, admission, shouldSchedule: true };
1104
- }
1105
- return { lane, admission: lane.pending, shouldSchedule: false };
1106
- };
1107
- const settleAdmission = (key, admission) => {
1108
- const lane = admissionLanes.get(key);
1109
- if (!lane)
1110
- return;
1111
- if (lane.head?.runId === admission.runId) {
1112
- lane.head = lane.pending;
1113
- lane.pending = undefined;
1114
- } else if (lane.pending?.runId === admission.runId) {
1115
- lane.pending = undefined;
1116
- }
1117
- if (!lane.head && !lane.pending)
1118
- admissionLanes.delete(key);
1119
- };
1120
- const waitForAdmissionAcceptances = async (lane) => {
1121
- while (true) {
1122
- const tail = lane.acceptanceTail;
1123
- await tail.catch(() => {
1124
- return;
1125
- });
1126
- if (tail === lane.acceptanceTail)
1127
- return;
1128
- }
1129
- };
1130
- const checkpointEveryEvents = config.loop?.checkpointEveryEvents ?? 20;
1131
- const maxSteps = config.loop?.maxSteps ?? 50;
1132
- const idleTimeoutMs = config.loop?.idleTimeoutMs;
1133
- if (!Number.isSafeInteger(checkpointEveryEvents) || checkpointEveryEvents < 1) {
1134
- throw new TypeError("checkpointEveryEvents must be a positive safe integer");
1135
- }
1136
- if (!Number.isSafeInteger(maxSteps) || maxSteps < 1) {
1137
- throw new TypeError("maxSteps must be a positive safe integer");
1138
- }
1139
- if (idleTimeoutMs !== undefined && (!Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs < 1)) {
1140
- throw new TypeError("idleTimeoutMs must be a positive safe integer");
1141
- }
1142
- const policyNames = new Set(["max-steps"]);
1143
- for (const policy of config.loop?.stopPolicies ?? []) {
1144
- if (!policy.name || policyNames.has(policy.name)) {
1145
- throw new TypeError("Agent stop policy names must be non-empty and unique");
1146
- }
1147
- policyNames.add(policy.name);
1148
- }
1149
- const publish = async (event) => {
1150
- try {
1151
- await config.publish?.(event);
1152
- } catch (error) {
1153
- try {
1154
- await config.onPublishError?.({ event, error });
1155
- } catch {}
1156
- }
1157
- };
1158
- const executeRun = async (input) => {
1151
+
1152
+ // src/agent-runtime/run-execution.ts
1153
+ function createRunExecutor(dependencies) {
1154
+ const {
1155
+ config,
1156
+ publish,
1157
+ runtimeEpoch,
1158
+ generateId,
1159
+ now,
1160
+ checkpointEveryEvents,
1161
+ maxSteps,
1162
+ idleTimeoutMs
1163
+ } = dependencies;
1164
+ return async function executeRun(input) {
1159
1165
  const queuedSnapshot = await config.store.loadSnapshot(input.acceptedRun.conversationId);
1160
1166
  const queuedRun = findRun(queuedSnapshot.runs, input.acceptedRun.id);
1161
1167
  const acquired = appliedSnapshot(await config.store.acquireRun({
@@ -1628,8 +1634,9 @@ function createAgentRuntime(config) {
1628
1634
  } else {
1629
1635
  terminalReason = "provider_failure";
1630
1636
  }
1637
+ } finally {
1638
+ idleDeadline.dispose();
1631
1639
  }
1632
- idleDeadline.dispose();
1633
1640
  assistant = AgentMessageSchema.parse({
1634
1641
  ...assistant,
1635
1642
  status: assistantStatus(terminalReason),
@@ -1699,10 +1706,110 @@ function createAgentRuntime(config) {
1699
1706
  ...terminalPolicyName && { policyName: terminalPolicyName }
1700
1707
  };
1701
1708
  };
1709
+ }
1710
+
1711
+ // src/agent-runtime/runtime.ts
1712
+ function createAgentRuntime(config) {
1713
+ const coordinator = createAgentSessionCoordinator();
1714
+ const tickets = new Map;
1715
+ const generateId = config.generateId ?? (() => crypto.randomUUID());
1716
+ const now = config.now ?? (() => new Date);
1717
+ const runtimeEpoch = generateId();
1718
+ const admissionLanes = createRuntimeAdmissionLanes();
1719
+ const reserveAdmission = admissionLanes.reserve;
1720
+ const settleAdmission = admissionLanes.settle;
1721
+ const waitForAdmissionAcceptances = admissionLanes.waitForAcceptances;
1722
+ const checkpointEveryEvents = config.loop?.checkpointEveryEvents ?? 20;
1723
+ const maxSteps = config.loop?.maxSteps ?? 50;
1724
+ const idleTimeoutMs = config.loop?.idleTimeoutMs;
1725
+ if (!Number.isSafeInteger(checkpointEveryEvents) || checkpointEveryEvents < 1) {
1726
+ throw new TypeError("checkpointEveryEvents must be a positive safe integer");
1727
+ }
1728
+ if (!Number.isSafeInteger(maxSteps) || maxSteps < 1) {
1729
+ throw new TypeError("maxSteps must be a positive safe integer");
1730
+ }
1731
+ if (idleTimeoutMs !== undefined && (!Number.isSafeInteger(idleTimeoutMs) || idleTimeoutMs < 1)) {
1732
+ throw new TypeError("idleTimeoutMs must be a positive safe integer");
1733
+ }
1734
+ const policyNames = new Set(["max-steps"]);
1735
+ for (const policy of config.loop?.stopPolicies ?? []) {
1736
+ if (!policy.name || policyNames.has(policy.name)) {
1737
+ throw new TypeError("Agent stop policy names must be non-empty and unique");
1738
+ }
1739
+ policyNames.add(policy.name);
1740
+ }
1741
+ const publish = async (event) => {
1742
+ try {
1743
+ await config.publish?.(event);
1744
+ } catch (error) {
1745
+ try {
1746
+ await config.onPublishError?.({ event, error });
1747
+ } catch {}
1748
+ }
1749
+ };
1750
+ const executeRun = createRunExecutor({
1751
+ config,
1752
+ publish,
1753
+ runtimeEpoch,
1754
+ generateId,
1755
+ now,
1756
+ checkpointEveryEvents,
1757
+ maxSteps,
1758
+ ...idleTimeoutMs !== undefined && { idleTimeoutMs }
1759
+ });
1760
+ let admissionClosed = false;
1761
+ const closedError = () => new Error("[stitchkit] agent runtime is closed and admits no further work");
1762
+ const admissionsInFlight = new Set;
1763
+ const beginAdmission = () => {
1764
+ const handoff = Promise.withResolvers();
1765
+ admissionsInFlight.add(handoff);
1766
+ return () => {
1767
+ if (admissionsInFlight.delete(handoff))
1768
+ handoff.resolve();
1769
+ };
1770
+ };
1771
+ const drainAdmissions = async (budgetMs) => {
1772
+ const pending = [...admissionsInFlight].map((handoff) => handoff.promise);
1773
+ if (pending.length === 0)
1774
+ return 0;
1775
+ let stranded = pending.length;
1776
+ const settled = Promise.all(pending.map((promise) => promise.then(() => {
1777
+ stranded -= 1;
1778
+ })));
1779
+ if (budgetMs === undefined) {
1780
+ await settled;
1781
+ return 0;
1782
+ }
1783
+ let timer;
1784
+ await Promise.race([
1785
+ settled,
1786
+ new Promise((resolve) => {
1787
+ timer = setTimeout(resolve, budgetMs);
1788
+ })
1789
+ ]);
1790
+ if (timer !== undefined)
1791
+ clearTimeout(timer);
1792
+ return stranded;
1793
+ };
1794
+ const refuse = () => {
1795
+ const error = closedError();
1796
+ const accepted = Promise.reject(error);
1797
+ const result = Promise.reject(error);
1798
+ accepted.catch(() => {
1799
+ return;
1800
+ });
1801
+ result.catch(() => {
1802
+ return;
1803
+ });
1804
+ return { accepted, result };
1805
+ };
1702
1806
  const resume = (rawInput) => {
1807
+ if (admissionClosed)
1808
+ return refuse();
1703
1809
  const context = config.protocol.parseContext(rawInput.context);
1704
1810
  const accepted = Promise.withResolvers();
1705
1811
  const result = Promise.withResolvers();
1812
+ const handedOff = beginAdmission();
1706
1813
  (async () => {
1707
1814
  try {
1708
1815
  const snapshot = await config.store.loadSnapshot(rawInput.conversationId);
@@ -1719,16 +1826,29 @@ function createAgentRuntime(config) {
1719
1826
  execute: () => executeRun({ acceptedRun: recoveredRun, context, signal })
1720
1827
  })
1721
1828
  });
1829
+ ticket.accepted.catch(() => {
1830
+ return;
1831
+ });
1722
1832
  ticket.result.then(result.resolve, result.reject);
1723
1833
  } catch (error) {
1724
1834
  accepted.reject(error);
1725
1835
  result.reject(error);
1836
+ } finally {
1837
+ handedOff();
1726
1838
  }
1727
1839
  })();
1728
1840
  return { accepted: accepted.promise, result: result.promise };
1729
1841
  };
1730
1842
  return {
1731
1843
  submit(rawInput) {
1844
+ if (admissionClosed) {
1845
+ const refused = refuse();
1846
+ const admission = Promise.reject(closedError());
1847
+ admission.catch(() => {
1848
+ return;
1849
+ });
1850
+ return { ...refused, admission };
1851
+ }
1732
1852
  const metadata = rawInput.metadata === undefined ? undefined : config.protocol.parseInputMetadata(rawInput.metadata);
1733
1853
  const input = {
1734
1854
  conversationId: rawInput.conversationId,
@@ -1801,6 +1921,7 @@ function createAgentRuntime(config) {
1801
1921
  tickets.delete(input.conversationId);
1802
1922
  };
1803
1923
  outerResult.promise.then(forgetTicket, forgetTicket);
1924
+ const handedOff = beginAdmission();
1804
1925
  (async () => {
1805
1926
  try {
1806
1927
  await previousAcceptance.catch(() => {
@@ -1810,6 +1931,8 @@ function createAgentRuntime(config) {
1810
1931
  context,
1811
1932
  conversationId: input.conversationId
1812
1933
  });
1934
+ if (admissionClosed)
1935
+ throw closedError();
1813
1936
  const acceptance = await config.store.acceptInputAndAssignRun({
1814
1937
  idempotencyKey: input.idempotencyKey,
1815
1938
  input: userMessage2,
@@ -1918,6 +2041,9 @@ function createAgentRuntime(config) {
1918
2041
  };
1919
2042
  }
1920
2043
  });
2044
+ ticket.accepted.catch(() => {
2045
+ return;
2046
+ });
1921
2047
  if (reservation) {
1922
2048
  ticket.result.then(reservation.admission.completion.resolve, reservation.admission.completion.reject);
1923
2049
  reservation.admission.completion.promise.then((value) => {
@@ -1940,6 +2066,7 @@ function createAgentRuntime(config) {
1940
2066
  }
1941
2067
  } finally {
1942
2068
  acceptanceDone.resolve();
2069
+ handedOff();
1943
2070
  }
1944
2071
  })();
1945
2072
  return publicTicket;
@@ -1969,9 +2096,8 @@ function createAgentRuntime(config) {
1969
2096
  return requested;
1970
2097
  },
1971
2098
  async recover(options) {
1972
- if (!config.store.scanRecoverablePage) {
1973
- throw new Error("The configured agent store does not support bounded recovery scans");
1974
- }
2099
+ if (admissionClosed)
2100
+ throw closedError();
1975
2101
  const pageSize = options.pageSize ?? 100;
1976
2102
  const maxRuns = options.maxRuns ?? 1000;
1977
2103
  if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 1000) {
@@ -1982,14 +2108,17 @@ function createAgentRuntime(config) {
1982
2108
  }
1983
2109
  const outcomes = [];
1984
2110
  let cursor;
1985
- while (outcomes.length < maxRuns && !options.signal?.aborted) {
1986
- const page = await config.store.scanRecoverablePage({
2111
+ while (outcomes.length < maxRuns && !options.signal?.aborted && !admissionClosed) {
2112
+ const page = await config.store.scanRecoverable({
1987
2113
  ...cursor && { cursor },
1988
2114
  limit: Math.min(pageSize, maxRuns - outcomes.length)
1989
2115
  });
1990
2116
  for (const item of page.items) {
1991
2117
  if (options.signal?.aborted)
1992
2118
  break;
2119
+ if (admissionClosed)
2120
+ break;
2121
+ const handedOff = beginAdmission();
1993
2122
  try {
1994
2123
  if (item.run.state === "queued") {
1995
2124
  const snapshot = await config.store.loadSnapshot(item.conversationId);
@@ -2004,6 +2133,8 @@ function createAgentRuntime(config) {
2004
2133
  }
2005
2134
  }
2006
2135
  const decision = await options.decide?.(item) ?? (item.run.state === "queued" ? { action: "resume" } : { action: "skip" });
2136
+ if (admissionClosed)
2137
+ throw closedError();
2007
2138
  if (decision.action === "skip") {
2008
2139
  outcomes.push({
2009
2140
  conversationId: item.conversationId,
@@ -2063,6 +2194,8 @@ function createAgentRuntime(config) {
2063
2194
  outcome: "failed",
2064
2195
  error
2065
2196
  });
2197
+ } finally {
2198
+ handedOff();
2066
2199
  }
2067
2200
  }
2068
2201
  cursor = page.nextCursor;
@@ -2072,7 +2205,23 @@ function createAgentRuntime(config) {
2072
2205
  return outcomes;
2073
2206
  },
2074
2207
  stop: (conversationKey, reason) => coordinator.stop(conversationKey, reason),
2075
- close: (options) => coordinator.close(options)
2208
+ close: async (options = {}) => {
2209
+ assertCloseBudgets(options);
2210
+ admissionClosed = true;
2211
+ const startedAt = performance.now();
2212
+ const stranded = await drainAdmissions(options.forceTimeoutMs);
2213
+ const spent = Math.max(0, Math.round(performance.now() - startedAt));
2214
+ const remainingBudget = (value) => value === undefined ? undefined : Math.max(0, value - spent);
2215
+ const grace = remainingBudget(options.gracePeriodMs);
2216
+ const force = remainingBudget(options.forceTimeoutMs);
2217
+ const result = await coordinator.close({
2218
+ ...grace !== undefined && { gracePeriodMs: grace },
2219
+ ...force !== undefined && { forceTimeoutMs: force }
2220
+ });
2221
+ if (stranded === 0)
2222
+ return result;
2223
+ return { settled: false, timedOut: true, remaining: result.remaining + stranded };
2224
+ }
2076
2225
  };
2077
2226
  }
2078
2227
  // src/agent-runtime/store.ts
@@ -2202,14 +2351,31 @@ function emptyHead(conversationId) {
2202
2351
  version: 0
2203
2352
  });
2204
2353
  }
2354
+ function historyPositions(messages) {
2355
+ const positions = new Map;
2356
+ messages.forEach((message, index) => {
2357
+ if (!positions.has(message.id))
2358
+ positions.set(message.id, index);
2359
+ });
2360
+ return (run) => {
2361
+ let earliest = Number.MAX_SAFE_INTEGER;
2362
+ for (const id of [...run.inputMessageIds, run.assistantMessageId]) {
2363
+ const position = positions.get(id);
2364
+ if (position !== undefined && position < earliest)
2365
+ earliest = position;
2366
+ }
2367
+ return earliest;
2368
+ };
2369
+ }
2205
2370
  function snapshotOf(head, messages, records) {
2206
2371
  validateSnapshot(head, messages, records);
2372
+ const positionOf = historyPositions(messages);
2207
2373
  return AgentSnapshotSchema.parse({
2208
2374
  schemaVersion: 1,
2209
2375
  conversationId: head.conversationId,
2210
2376
  version: head.version,
2211
2377
  messages,
2212
- runs: records.map((record) => record.run).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id))
2378
+ runs: records.map((record) => record.run).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || positionOf(left) - positionOf(right) || left.id.localeCompare(right.id))
2213
2379
  });
2214
2380
  }
2215
2381
  function validateSnapshot(head, messages, records) {
@@ -2271,14 +2437,6 @@ function terminalState(reason) {
2271
2437
  return "abandoned";
2272
2438
  return "failed";
2273
2439
  }
2274
- function terminalMessageStatus2(reason) {
2275
- if (reason === "success" || reason === "policy_stop")
2276
- return "completed";
2277
- if (reason === "interrupted" || reason === "cancelled" || reason === "shutdown") {
2278
- return "interrupted";
2279
- }
2280
- return "failed";
2281
- }
2282
2440
  function applied(current, input, effects) {
2283
2441
  return {
2284
2442
  outcome: "applied",
@@ -2439,7 +2597,7 @@ function reduceStore(current, operation) {
2439
2597
  }
2440
2598
  if (operation.type === "terminal" && run) {
2441
2599
  const input = operation.input;
2442
- if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== terminalMessageStatus2(input.reason)) {
2600
+ if (run.revision !== input.expectedRevision || run.state !== "running" && run.state !== "interrupt_requested" || run.ownerId !== input.ownerId || input.fencingToken !== undefined && run.fencingToken !== input.fencingToken || input.assistant.runId !== run.id || input.assistant.id !== run.assistantMessageId || input.assistant.conversationId !== run.conversationId || input.assistant.role !== "assistant" || input.assistant.status !== assistantStatus(input.reason)) {
2443
2601
  return conflict(run.revision);
2444
2602
  }
2445
2603
  const next = AgentRunSchema.parse({
@@ -2645,26 +2803,7 @@ function createAgentRuntimeStore(driver) {
2645
2803
  type: "compact",
2646
2804
  input: ReplaceCompactedRangeSchema.parse(input)
2647
2805
  }),
2648
- async scanRecoverable() {
2649
- const snapshots = [];
2650
- const seenConversationIds = new Set;
2651
- let cursor;
2652
- do {
2653
- const page = AgentRecoverablePageSchema.parse(await driver.scanRecoverable({
2654
- ...cursor && { cursor },
2655
- limit: 100
2656
- }));
2657
- const conversationIds = [
2658
- ...new Set(page.items.map((item) => item.conversationId))
2659
- ].filter((conversationId) => !seenConversationIds.has(conversationId));
2660
- for (const conversationId of conversationIds)
2661
- seenConversationIds.add(conversationId);
2662
- snapshots.push(...await Promise.all(conversationIds.map(loadSnapshot)));
2663
- cursor = page.nextCursor;
2664
- } while (cursor !== undefined);
2665
- return snapshots;
2666
- },
2667
- async scanRecoverablePage(input) {
2806
+ async scanRecoverable(input) {
2668
2807
  const parsed = AgentRecoverableScanInputSchema.parse(input);
2669
2808
  return AgentRecoverablePageSchema.parse(await driver.scanRecoverable(parsed));
2670
2809
  }
@@ -2863,6 +3002,7 @@ export {
2863
3002
  AgentRunSchema,
2864
3003
  AgentRunStateEventSchema,
2865
3004
  AgentRunStateSchema,
3005
+ AgentRuntimeConflictError,
2866
3006
  AgentRuntimeEventCursorSchema,
2867
3007
  AgentRuntimeEventSchema,
2868
3008
  AgentRuntimeHeadSchema,