@kortyx/runtime 0.11.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  var index_exports = {};
21
21
  __export(index_exports, {
22
22
  buildInitialGraphState: () => buildInitialGraphState,
23
+ captureGraphSnapshot: () => captureGraphSnapshot,
23
24
  clearRegisteredNodes: () => clearRegisteredNodes,
24
25
  createExecutionGraph: () => createExecutionGraph,
25
26
  createFileWorkflowRegistry: () => createFileWorkflowRegistry,
@@ -35,7 +36,8 @@ __export(index_exports, {
35
36
  makeRequestId: () => makeRequestId,
36
37
  makeResumeToken: () => makeResumeToken,
37
38
  registerNode: () => registerNode,
38
- resolveNode: () => resolveNode
39
+ resolveNode: () => resolveNode,
40
+ restoreGraphSnapshot: () => restoreGraphSnapshot
39
41
  });
40
42
  module.exports = __toCommonJS(index_exports);
41
43
 
@@ -100,7 +102,7 @@ function createInMemoryCheckpointSaver(options) {
100
102
  }
101
103
  };
102
104
  }
103
- return tuple;
105
+ return JSON.parse(JSON.stringify(tuple));
104
106
  },
105
107
  async *list(_config, _options) {
106
108
  },
@@ -195,6 +197,12 @@ function createInMemoryPendingRequestStore() {
195
197
  }
196
198
  };
197
199
  return {
200
+ async take(token) {
201
+ prune();
202
+ const record = store.get(token);
203
+ store.delete(token);
204
+ return record ? JSON.parse(JSON.stringify(record)) : null;
205
+ },
198
206
  async save(rec) {
199
207
  prune();
200
208
  store.set(rec.token, rec);
@@ -219,6 +227,12 @@ function createRedisPendingRequestStore(options) {
219
227
  const prefix = options.prefix ?? "kortyx:pending:";
220
228
  const key = (token) => `${prefix}${token}`;
221
229
  return {
230
+ ...options.store.take ? {
231
+ take: async (token) => {
232
+ const raw = await options.store.take(key(token));
233
+ return raw ? JSON.parse(raw) : null;
234
+ }
235
+ } : {},
222
236
  async save(rec) {
223
237
  await options.store.set(key(rec.token), JSON.stringify(rec), rec.ttlMs);
224
238
  },
@@ -586,6 +600,11 @@ function createRedisFrameworkStore(options) {
586
600
  const prefix = options.prefix ?? "kortyx:fw:";
587
601
  const k = (key) => `${prefix}${key}`;
588
602
  return {
603
+ async take(key) {
604
+ const r = await client.command("GETDEL", [k(key)]);
605
+ if (isRedisError(r)) throw new Error(`Redis GETDEL error: ${r.message}`);
606
+ return typeof r === "string" ? r : null;
607
+ },
589
608
  async get(key) {
590
609
  const r = await client.command("GET", [k(key)]);
591
610
  if (r === null) return null;
@@ -828,8 +847,12 @@ function createRedisSessionCheckpointStore(options) {
828
847
  if (!source) throw new Error(`Checkpoint "${id}" not found.`);
829
848
  const sessionId = options2?.newSessionId || createId("session");
830
849
  const { parentCheckpointId: _parentCheckpointId, ...sourceRecord } = clone(source);
850
+ const runId = source.activePendingRequests.some(
851
+ (request) => request.graphSnapshot
852
+ ) ? `run-${(0, import_node_crypto.randomUUID)()}` : source.runId;
831
853
  const forked = {
832
854
  ...sourceRecord,
855
+ runId,
833
856
  id: createId("cp"),
834
857
  sessionId,
835
858
  parentSessionId: source.sessionId,
@@ -837,6 +860,7 @@ function createRedisSessionCheckpointStore(options) {
837
860
  activePendingRequests: clone(source.activePendingRequests).map(
838
861
  (request) => ({
839
862
  ...request,
863
+ runId,
840
864
  token: createResumeToken(),
841
865
  requestId: createId("human"),
842
866
  sessionId
@@ -991,8 +1015,12 @@ function createInMemorySessionCheckpointStore(options) {
991
1015
  }
992
1016
  const sessionId = options2?.newSessionId || createSessionId();
993
1017
  const { parentCheckpointId: _parentCheckpointId, ...sourceRecord } = clone2(source);
1018
+ const runId = source.activePendingRequests.some(
1019
+ (request) => request.graphSnapshot
1020
+ ) ? `run-${(0, import_node_crypto2.randomUUID)()}` : source.runId;
994
1021
  const forked = {
995
1022
  ...sourceRecord,
1023
+ runId,
996
1024
  id: createCheckpointId(),
997
1025
  sessionId,
998
1026
  parentSessionId: source.sessionId,
@@ -1000,6 +1028,7 @@ function createInMemorySessionCheckpointStore(options) {
1000
1028
  activePendingRequests: clone2(source.activePendingRequests).map(
1001
1029
  (request) => ({
1002
1030
  ...request,
1031
+ runId,
1003
1032
  token: createResumeToken2(),
1004
1033
  requestId: createRequestId(),
1005
1034
  sessionId
@@ -1089,10 +1118,44 @@ function createFrameworkAdapterFromEnv(env = process.env) {
1089
1118
  return createInMemoryFrameworkAdapter({ ...ttlMs ? { ttlMs } : {} });
1090
1119
  }
1091
1120
 
1121
+ // src/framework/graph-snapshot.ts
1122
+ async function captureGraphSnapshot(saver, threadId, checkpointId) {
1123
+ const tuple = await saver.getTuple({
1124
+ configurable: {
1125
+ thread_id: threadId,
1126
+ checkpoint_ns: "",
1127
+ ...checkpointId ? { checkpoint_id: checkpointId } : {}
1128
+ }
1129
+ });
1130
+ return tuple ? JSON.parse(JSON.stringify(tuple)) : void 0;
1131
+ }
1132
+ async function restoreGraphSnapshot(saver, snapshot, threadId) {
1133
+ const copy = JSON.parse(JSON.stringify(snapshot));
1134
+ const config = await saver.put(
1135
+ { configurable: { thread_id: threadId, checkpoint_ns: "" } },
1136
+ copy.checkpoint,
1137
+ copy.metadata,
1138
+ copy.checkpoint.channel_versions
1139
+ );
1140
+ const reset = saver;
1141
+ if (reset.deleteCheckpointWrites)
1142
+ await reset.deleteCheckpointWrites(threadId, "", copy.checkpoint.id);
1143
+ const writes = /* @__PURE__ */ new Map();
1144
+ for (const [task, channel, value] of copy.pendingWrites ?? []) {
1145
+ const group = writes.get(task) ?? [];
1146
+ group.push([channel, value]);
1147
+ writes.set(task, group);
1148
+ }
1149
+ for (const [task, group] of writes)
1150
+ await saver.putWrites(config, group, task);
1151
+ }
1152
+
1092
1153
  // src/graph/create-execution-graph.ts
1093
- var import_hooks = require("@kortyx/hooks");
1154
+ var import_promises = require("timers/promises");
1155
+ var import_core2 = require("@kortyx/core");
1156
+ var import_hooks2 = require("@kortyx/hooks");
1094
1157
  var import_utils = require("@kortyx/utils");
1095
- var import_langgraph = require("@langchain/langgraph");
1158
+ var import_langgraph2 = require("@langchain/langgraph");
1096
1159
 
1097
1160
  // src/node-loader.ts
1098
1161
  var import_node_path = require("path");
@@ -1174,31 +1237,250 @@ async function resolveNodeHandler(args) {
1174
1237
  return resolveNode(run);
1175
1238
  }
1176
1239
 
1240
+ // src/graph/call-workflow.ts
1241
+ var import_core = require("@kortyx/core");
1242
+ var import_hooks = require("@kortyx/hooks");
1243
+ var import_singletons = require("@langchain/core/singletons");
1244
+ var import_langgraph = require("@langchain/langgraph");
1245
+ function assertSequentialWorkflow(workflow) {
1246
+ const groups = /* @__PURE__ */ new Map();
1247
+ for (const [from, , condition] of workflow.edges) {
1248
+ const group = groups.get(from) ?? { plain: 0, conditional: 0 };
1249
+ if (condition) group.conditional++;
1250
+ else group.plain++;
1251
+ groups.set(from, group);
1252
+ if (group.plain > 1 || group.plain > 0 && group.conditional > 0) {
1253
+ throw new Error(
1254
+ `Workflow '${workflow.id}' has parallel edges; child calls require sequential graphs.`
1255
+ );
1256
+ }
1257
+ }
1258
+ }
1259
+ function createWorkflowCallService(config, parent, execution = {}) {
1260
+ return async (args) => {
1261
+ const execute = async () => {
1262
+ (0, import_core.throwIfExecutionAborted)(execution.abortSignal);
1263
+ if (parent) assertSequentialWorkflow(parent);
1264
+ const depth = (config.workflowCallDepth ?? 0) + 1;
1265
+ if (depth > 16)
1266
+ throw new Error("Maximum child workflow depth (16) exceeded.");
1267
+ const workflow = await config.selectWorkflow?.(args.workflow);
1268
+ if (!workflow || workflow.id !== args.workflow)
1269
+ throw new Error(`Workflow '${args.workflow}' is not registered.`);
1270
+ if (!workflow.inputSchema || !workflow.outputSchema)
1271
+ throw new Error(
1272
+ `Callable workflow '${workflow.id}' requires inputSchema and outputSchema.`
1273
+ );
1274
+ if (args.definition && (args.definition.version !== workflow.version || args.definition.inputSchema !== workflow.inputSchema || args.definition.outputSchema !== workflow.outputSchema)) {
1275
+ throw new Error(
1276
+ "The typed workflow contract does not match the registered definition."
1277
+ );
1278
+ }
1279
+ assertSequentialWorkflow(workflow);
1280
+ const snapshot = args.snapshot;
1281
+ if (snapshot && snapshot.version !== workflow.version)
1282
+ throw new Error(
1283
+ `Workflow '${workflow.id}' changed since the call was suspended.`
1284
+ );
1285
+ const input = snapshot ? void 0 : workflow.inputSchema.parse(args.input);
1286
+ if (!snapshot) (0, import_hooks.workflowCallFingerprint)(input);
1287
+ const saver = createInMemoryCheckpointSaver();
1288
+ const threadId = "child";
1289
+ let request;
1290
+ const invocationPath = `${config.invocationPath ?? "root"}/${encodeURIComponent(args.id)}:${args.invocationId}`;
1291
+ let childConfig = {
1292
+ executionBranchId: config.executionBranchId,
1293
+ prepareChildTelemetry: config.prepareChildTelemetry,
1294
+ context: config.context,
1295
+ session: config.session,
1296
+ selectWorkflow: config.selectWorkflow,
1297
+ getProvider: config.getProvider,
1298
+ telemetry: config.telemetry ? {
1299
+ ...config.telemetry,
1300
+ correlation: {
1301
+ runId: config.telemetry.correlation?.runId,
1302
+ sessionId: config.telemetry.correlation?.sessionId,
1303
+ workflowId: workflow.id,
1304
+ invocationId: args.invocationId,
1305
+ parentInvocationId: config.telemetry.correlation?.invocationId,
1306
+ branchId: config.executionBranchId ?? config.executionRunId
1307
+ }
1308
+ } : void 0,
1309
+ reasonTrace: config.reasonTrace,
1310
+ checkpointer: saver,
1311
+ executionRunId: config.executionRunId,
1312
+ workflowCallDepth: depth,
1313
+ invocationPath,
1314
+ emit: (event, payload) => {
1315
+ if (event === "interrupt") {
1316
+ const original = payload.input;
1317
+ request = {
1318
+ ...original,
1319
+ meta: {
1320
+ ...original.meta,
1321
+ workflowCallPath: [
1322
+ {
1323
+ workflowId: workflow.id,
1324
+ invocationId: args.invocationId,
1325
+ callId: args.id
1326
+ },
1327
+ ...Array.isArray(original.meta?.workflowCallPath) ? original.meta.workflowCallPath : []
1328
+ ],
1329
+ workflowCall: original.meta?.workflowCall ?? {
1330
+ invocationId: args.invocationId,
1331
+ workflowId: workflow.id,
1332
+ nodeId: payload.node
1333
+ }
1334
+ }
1335
+ };
1336
+ return;
1337
+ }
1338
+ if (event === "error")
1339
+ throw new Error(
1340
+ String(
1341
+ payload.message ?? "Child workflow failed."
1342
+ )
1343
+ );
1344
+ if (event === "transition")
1345
+ throw new Error(
1346
+ "transitionTo is not supported inside child workflows; useWorkflow returns to its caller."
1347
+ );
1348
+ const value = payload;
1349
+ const scoped = { ...value, workflow: workflow.id, invocationPath };
1350
+ for (const key of ["node", "streamId", "opId", "segmentId"]) {
1351
+ if (typeof value[key] === "string")
1352
+ scoped[key] = `${config.executionRunId ?? "run"}/${args.invocationId}/${value[key]}`;
1353
+ }
1354
+ config.emit?.(event, scoped);
1355
+ }
1356
+ };
1357
+ childConfig = config.prepareChildTelemetry?.(workflow, childConfig) ?? childConfig;
1358
+ if (snapshot) {
1359
+ if (snapshot.limitPaused)
1360
+ snapshot.checkpoint.checkpoint.channel_values.config = childConfig;
1361
+ await restoreGraphSnapshot(saver, snapshot.checkpoint, threadId);
1362
+ }
1363
+ const graph = await createExecutionGraph(
1364
+ workflow,
1365
+ childConfig,
1366
+ execution
1367
+ );
1368
+ const initial = {
1369
+ input,
1370
+ data: {},
1371
+ runtime: {},
1372
+ config: childConfig,
1373
+ currentWorkflow: workflow.id,
1374
+ lastNode: "__start__",
1375
+ awaitingHumanInput: false,
1376
+ conversationHistory: []
1377
+ };
1378
+ const resumePatch = snapshot?.request?.meta?.__kortyxResumeStatePatch;
1379
+ const command = snapshot?.limitPaused ? null : snapshot ? new import_langgraph.Command({
1380
+ resume: args.response,
1381
+ update: {
1382
+ config: childConfig,
1383
+ ...resumePatch ? { runtime: resumePatch } : {}
1384
+ }
1385
+ }) : initial;
1386
+ let result;
1387
+ try {
1388
+ result = await import_singletons.AsyncLocalStorageProviderSingleton.runWithConfig(
1389
+ {},
1390
+ () => graph.invoke(command, {
1391
+ configurable: { thread_id: threadId, checkpoint_ns: "" },
1392
+ callbacks: []
1393
+ })
1394
+ );
1395
+ } catch (error) {
1396
+ (0, import_core.throwIfExecutionAborted)(execution.abortSignal);
1397
+ if ((0, import_core.isExecutionLimitReached)(error)) {
1398
+ const checkpoint = await captureGraphSnapshot(saver, threadId);
1399
+ if (checkpoint) {
1400
+ const state = checkpoint.checkpoint.channel_values;
1401
+ const patch = error.__kortyxHookStatePatch;
1402
+ if (patch) state.runtime = { ...state.runtime, ...patch };
1403
+ Object.assign(error, {
1404
+ __kortyxChildSnapshot: {
1405
+ version: workflow.version,
1406
+ checkpoint,
1407
+ limitPaused: true
1408
+ },
1409
+ __kortyxChildUsage: state.runtime.tokenUsage
1410
+ });
1411
+ }
1412
+ }
1413
+ throw error;
1414
+ }
1415
+ (0, import_core.throwIfExecutionAborted)(execution.abortSignal);
1416
+ if (request) {
1417
+ const checkpoint = await captureGraphSnapshot(saver, threadId);
1418
+ if (!checkpoint)
1419
+ throw new Error("Child interrupted without a graph checkpoint.");
1420
+ return {
1421
+ status: "interrupted",
1422
+ usage: request.meta?.__kortyxResumeStatePatch?.tokenUsage ?? result.runtime.tokenUsage,
1423
+ request,
1424
+ snapshot: {
1425
+ version: workflow.version,
1426
+ checkpoint,
1427
+ request
1428
+ }
1429
+ };
1430
+ }
1431
+ const data = workflow.outputSchema.parse(result.data ?? {});
1432
+ (0, import_hooks.workflowCallFingerprint)(data);
1433
+ return {
1434
+ status: "completed",
1435
+ data,
1436
+ usage: result.runtime.tokenUsage
1437
+ };
1438
+ };
1439
+ const trace = config.telemetry?.trace ?? config.reasonTrace;
1440
+ return trace?.withSpan ? trace.withSpan(
1441
+ {
1442
+ name: "kortyx.workflow.call",
1443
+ attributes: {
1444
+ workflowId: args.workflow,
1445
+ runId: config.executionRunId,
1446
+ branchId: config.executionBranchId ?? config.executionRunId,
1447
+ parentInvocationId: config.telemetry?.correlation?.invocationId,
1448
+ callerNodeExecutionId: args.callerNodeExecutionId,
1449
+ callId: args.id,
1450
+ invocationId: args.invocationId
1451
+ }
1452
+ },
1453
+ execute
1454
+ ) : execute();
1455
+ };
1456
+ }
1457
+
1177
1458
  // src/graph/create-execution-graph.ts
1178
- async function createExecutionGraph(workflow, config) {
1179
- const StateAnnotation = import_langgraph.Annotation.Root({
1180
- input: import_langgraph.Annotation,
1181
- output: import_langgraph.Annotation,
1182
- lastNode: import_langgraph.Annotation,
1183
- lastCondition: import_langgraph.Annotation,
1184
- lastIntent: import_langgraph.Annotation,
1185
- runtime: (0, import_langgraph.Annotation)({
1459
+ async function createExecutionGraph(workflow, config, execution = {}) {
1460
+ const StateAnnotation = import_langgraph2.Annotation.Root({
1461
+ currentWorkflow: import_langgraph2.Annotation,
1462
+ input: import_langgraph2.Annotation,
1463
+ output: import_langgraph2.Annotation,
1464
+ lastNode: import_langgraph2.Annotation,
1465
+ lastCondition: import_langgraph2.Annotation,
1466
+ lastIntent: import_langgraph2.Annotation,
1467
+ runtime: (0, import_langgraph2.Annotation)({
1186
1468
  reducer: (l, r) => (0, import_utils.deepMergeWithArrayOverwrite)(l ?? {}, r ?? {}),
1187
1469
  default: () => ({})
1188
1470
  }),
1189
- config: import_langgraph.Annotation,
1190
- transitionTo: import_langgraph.Annotation,
1191
- data: (0, import_langgraph.Annotation)({
1471
+ config: import_langgraph2.Annotation,
1472
+ transitionTo: import_langgraph2.Annotation,
1473
+ data: (0, import_langgraph2.Annotation)({
1192
1474
  reducer: (l, r) => (0, import_utils.deepMergeWithArrayOverwrite)(l ?? {}, r ?? {}),
1193
1475
  default: () => ({})
1194
1476
  }),
1195
- conversationHistory: (0, import_langgraph.Annotation)({
1477
+ conversationHistory: (0, import_langgraph2.Annotation)({
1196
1478
  reducer: (l, r) => [...l ?? [], ...r ?? []],
1197
1479
  default: () => []
1198
1480
  }),
1199
- awaitingHumanInput: import_langgraph.Annotation
1481
+ awaitingHumanInput: import_langgraph2.Annotation
1200
1482
  });
1201
- const builder = new import_langgraph.StateGraph(StateAnnotation);
1483
+ const builder = new import_langgraph2.StateGraph(StateAnnotation);
1202
1484
  const edgeApi = builder;
1203
1485
  const workflowName = workflow.id;
1204
1486
  const runtimeConfig = config;
@@ -1243,14 +1525,41 @@ async function createExecutionGraph(workflow, config) {
1243
1525
  );
1244
1526
  const behavior = nodeConfig.behavior ?? {};
1245
1527
  builder.addNode(nodeId, async (state) => {
1528
+ (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1246
1529
  const emitRuntimeEvent = (event, payload) => {
1247
1530
  runtimeConfig.emit(event, payload);
1248
1531
  };
1532
+ let suspension;
1249
1533
  const hookNodeContext = {
1534
+ abortSignal: execution.abortSignal,
1535
+ consumeExecution: (limit) => {
1536
+ (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1537
+ if (execution.budget) (0, import_core2.consumeExecutionBudget)(execution.budget, limit);
1538
+ },
1539
+ workflowCallTelemetry: runtimeConfig.telemetry ? {
1540
+ ...runtimeConfig.telemetry,
1541
+ correlation: {
1542
+ ...runtimeConfig.telemetry.correlation,
1543
+ runId: runtimeConfig.executionRunId,
1544
+ branchId: runtimeConfig.executionBranchId ?? runtimeConfig.executionRunId
1545
+ }
1546
+ } : void 0,
1547
+ ...runtimeConfig.selectWorkflow ? {
1548
+ callWorkflow: createWorkflowCallService(
1549
+ {
1550
+ ...runtimeConfig,
1551
+ context: state.config?.context ?? runtimeConfig.context
1552
+ },
1553
+ workflow,
1554
+ execution
1555
+ )
1556
+ } : {},
1250
1557
  graph: { name: workflowName, node: nodeId },
1251
1558
  config: nodeConfig,
1252
1559
  emit: emitRuntimeEvent,
1253
1560
  awaitInterrupt: (interruptConfig) => {
1561
+ (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1562
+ if (execution.budget) (0, import_core2.assertExecutionBudget)(execution.budget);
1254
1563
  const { kind, question } = interruptConfig;
1255
1564
  const isMulti = kind === "multi-choice" || interruptConfig.kind !== "text" && interruptConfig.multiple === true;
1256
1565
  const sharedInterruptFields = {
@@ -1272,8 +1581,9 @@ async function createExecutionGraph(workflow, config) {
1272
1581
  };
1273
1582
  let resumed;
1274
1583
  try {
1275
- resumed = (0, import_langgraph.interrupt)(payload);
1584
+ resumed = (0, import_langgraph2.interrupt)(payload);
1276
1585
  } catch (error) {
1586
+ suspension = error;
1277
1587
  emitRuntimeEvent("interrupt", {
1278
1588
  node: nodeId,
1279
1589
  workflow: workflowName,
@@ -1307,6 +1617,7 @@ async function createExecutionGraph(workflow, config) {
1307
1617
  let retryHookRuntime = null;
1308
1618
  while (attempt < maxAttempts) {
1309
1619
  try {
1620
+ (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1310
1621
  attempt++;
1311
1622
  const attemptState = retryHookRuntime && typeof retryHookRuntime === "object" ? {
1312
1623
  ...state,
@@ -1321,13 +1632,23 @@ async function createExecutionGraph(workflow, config) {
1321
1632
  state: attemptState,
1322
1633
  reasonTrace: activeTrace
1323
1634
  };
1324
- const runNode = () => (0, import_hooks.runWithHookContext)(
1325
- hookContext,
1326
- async () => resolvedRun({
1327
- input: state.input,
1328
- params: nodeParams ?? {}
1329
- })
1330
- );
1635
+ const runNode = () => (0, import_hooks2.runWithHookContext)(hookContext, async () => {
1636
+ try {
1637
+ hookNodeContext.consumeExecution("maxNodeExecutions");
1638
+ const result = await resolvedRun({
1639
+ input: state.input,
1640
+ params: nodeParams ?? {}
1641
+ });
1642
+ (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1643
+ if (execution.budget) (0, import_core2.assertExecutionBudget)(execution.budget);
1644
+ if (suspension) throw suspension;
1645
+ return result;
1646
+ } catch (error) {
1647
+ (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1648
+ if (execution.budget) (0, import_core2.assertExecutionBudget)(execution.budget);
1649
+ throw error;
1650
+ }
1651
+ });
1331
1652
  const context = configContext();
1332
1653
  const spanArgs = {
1333
1654
  name: "kortyx.node",
@@ -1339,6 +1660,8 @@ async function createExecutionGraph(workflow, config) {
1339
1660
  workflowRevisionId: telemetryCorrelation()?.workflowRevisionId
1340
1661
  } : {},
1341
1662
  ...typeof telemetryCorrelation()?.topologyHash === "string" ? { topologyHash: telemetryCorrelation()?.topologyHash } : {},
1663
+ invocationId: runtimeConfig.telemetry?.correlation?.invocationId,
1664
+ branchId: runtimeConfig.executionBranchId ?? runtimeConfig.executionRunId,
1342
1665
  nodeId,
1343
1666
  attempt,
1344
1667
  ...typeof context?.userId === "string" ? { userId: context.userId } : {},
@@ -1369,15 +1692,22 @@ async function createExecutionGraph(workflow, config) {
1369
1692
  patch
1370
1693
  );
1371
1694
  }
1695
+ (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1696
+ if ((0, import_core2.isExecutionLimitReached)(err)) {
1697
+ Object.assign(err, { nodeId, workflowId: workflowName });
1698
+ throw err;
1699
+ }
1700
+ if ((0, import_core2.isExecutionCancelled)(err) || (0, import_langgraph2.isGraphInterrupt)(err)) throw err;
1372
1701
  const hasMore = attempt < maxAttempts;
1373
1702
  const delayMs = behavior.retry?.delayMs ?? 0;
1374
1703
  if (hasMore && delayMs > 0) {
1375
- await new Promise((r) => setTimeout(r, delayMs));
1704
+ await (0, import_promises.setTimeout)(delayMs, void 0, { signal: execution.abortSignal });
1376
1705
  continue;
1377
1706
  }
1378
1707
  if (!hasMore) throw err;
1379
1708
  }
1380
1709
  }
1710
+ (0, import_core2.throwIfExecutionAborted)(execution.abortSignal);
1381
1711
  const res = nodeResult ?? {};
1382
1712
  if (hookRuntimeUpdates) {
1383
1713
  res.infra = {
@@ -1490,6 +1820,7 @@ async function createExecutionGraph(workflow, config) {
1490
1820
  const runtimeGraph = graph;
1491
1821
  runtimeGraph.name = workflow.id;
1492
1822
  runtimeGraph.config = config;
1823
+ runtimeGraph.execution = execution;
1493
1824
  runtimeGraph.resume = async (state, input) => {
1494
1825
  const resumed = { ...state, input, awaitingHumanInput: false };
1495
1826
  return await graph.invoke(resumed);
@@ -1515,9 +1846,9 @@ function makeRequestId(prefix = "req") {
1515
1846
  }
1516
1847
 
1517
1848
  // src/registry/file-registry.ts
1518
- var import_promises = require("fs/promises");
1849
+ var import_promises2 = require("fs/promises");
1519
1850
  var import_node_path2 = require("path");
1520
- var import_core = require("@kortyx/core");
1851
+ var import_core3 = require("@kortyx/core");
1521
1852
  var DEFAULT_EXTENSIONS = [
1522
1853
  ".workflow.ts",
1523
1854
  ".workflow.mts",
@@ -1541,15 +1872,15 @@ function createFileWorkflowRegistry(options) {
1541
1872
  let workflowCache = null;
1542
1873
  async function loadWorkflows() {
1543
1874
  if (shouldCache && workflowCache) return workflowCache;
1544
- const files = await (0, import_promises.readdir)(workflowsDir, { withFileTypes: true });
1875
+ const files = await (0, import_promises2.readdir)(workflowsDir, { withFileTypes: true });
1545
1876
  const workflowFiles = files.filter((entry) => entry.isFile()).map((entry) => entry.name).filter((file) => isWorkflowFile(file, extensions));
1546
1877
  const workflows = {};
1547
1878
  for (const file of workflowFiles) {
1548
1879
  try {
1549
1880
  const modulePath = (0, import_node_path2.join)(workflowsDir, file);
1550
1881
  if (isDeclarativeWorkflowFile(file)) {
1551
- const raw = await (0, import_promises.readFile)(modulePath, "utf8");
1552
- const workflow = (0, import_core.loadWorkflow)(raw);
1882
+ const raw = await (0, import_promises2.readFile)(modulePath, "utf8");
1883
+ const workflow = (0, import_core3.loadWorkflow)(raw);
1553
1884
  workflow.metadata = {
1554
1885
  ...workflow.metadata ?? {},
1555
1886
  __filePath: modulePath
@@ -1660,6 +1991,7 @@ async function buildInitialGraphState({
1660
1991
  // Annotate the CommonJS export names for ESM import in node:
1661
1992
  0 && (module.exports = {
1662
1993
  buildInitialGraphState,
1994
+ captureGraphSnapshot,
1663
1995
  clearRegisteredNodes,
1664
1996
  createExecutionGraph,
1665
1997
  createFileWorkflowRegistry,
@@ -1675,6 +2007,7 @@ async function buildInitialGraphState({
1675
2007
  makeRequestId,
1676
2008
  makeResumeToken,
1677
2009
  registerNode,
1678
- resolveNode
2010
+ resolveNode,
2011
+ restoreGraphSnapshot
1679
2012
  });
1680
2013
  //# sourceMappingURL=index.js.map