zeitlich 0.2.38 → 0.2.39

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 (125) hide show
  1. package/README.md +18 -0
  2. package/dist/{activities-BKhMtKDd.d.ts → activities-Bmu7XnaG.d.ts} +4 -6
  3. package/dist/{activities-CDcwkRZs.d.cts → activities-ByBFLvm2.d.cts} +4 -6
  4. package/dist/adapter-id-BB-mmrts.d.cts +17 -0
  5. package/dist/adapter-id-BB-mmrts.d.ts +17 -0
  6. package/dist/adapter-id-CMwVrVqv.d.cts +17 -0
  7. package/dist/adapter-id-CMwVrVqv.d.ts +17 -0
  8. package/dist/adapter-id-CbY2zeSt.d.cts +17 -0
  9. package/dist/adapter-id-CbY2zeSt.d.ts +17 -0
  10. package/dist/adapters/thread/anthropic/index.cjs +140 -23
  11. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  12. package/dist/adapters/thread/anthropic/index.d.cts +8 -7
  13. package/dist/adapters/thread/anthropic/index.d.ts +8 -7
  14. package/dist/adapters/thread/anthropic/index.js +140 -24
  15. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  16. package/dist/adapters/thread/anthropic/workflow.cjs +8 -3
  17. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  18. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -4
  19. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -4
  20. package/dist/adapters/thread/anthropic/workflow.js +8 -4
  21. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  22. package/dist/adapters/thread/google-genai/index.cjs +140 -23
  23. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  24. package/dist/adapters/thread/google-genai/index.d.cts +5 -4
  25. package/dist/adapters/thread/google-genai/index.d.ts +5 -4
  26. package/dist/adapters/thread/google-genai/index.js +140 -24
  27. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  28. package/dist/adapters/thread/google-genai/workflow.cjs +8 -3
  29. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  30. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -4
  31. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -4
  32. package/dist/adapters/thread/google-genai/workflow.js +8 -4
  33. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  34. package/dist/adapters/thread/index.cjs +16 -0
  35. package/dist/adapters/thread/index.cjs.map +1 -0
  36. package/dist/adapters/thread/index.d.cts +34 -0
  37. package/dist/adapters/thread/index.d.ts +34 -0
  38. package/dist/adapters/thread/index.js +12 -0
  39. package/dist/adapters/thread/index.js.map +1 -0
  40. package/dist/adapters/thread/langchain/index.cjs +139 -24
  41. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  42. package/dist/adapters/thread/langchain/index.d.cts +8 -7
  43. package/dist/adapters/thread/langchain/index.d.ts +8 -7
  44. package/dist/adapters/thread/langchain/index.js +139 -25
  45. package/dist/adapters/thread/langchain/index.js.map +1 -1
  46. package/dist/adapters/thread/langchain/workflow.cjs +8 -3
  47. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  48. package/dist/adapters/thread/langchain/workflow.d.cts +5 -4
  49. package/dist/adapters/thread/langchain/workflow.d.ts +5 -4
  50. package/dist/adapters/thread/langchain/workflow.js +8 -4
  51. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  52. package/dist/index.cjs +266 -48
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +6 -6
  55. package/dist/index.d.ts +6 -6
  56. package/dist/index.js +263 -49
  57. package/dist/index.js.map +1 -1
  58. package/dist/{proxy-D_3x7RN4.d.cts → proxy-BAKzNGRq.d.cts} +1 -1
  59. package/dist/{proxy-CUlKSvZS.d.ts → proxy-DO_MXbY4.d.ts} +1 -1
  60. package/dist/{thread-manager-CVu7o2cs.d.ts → thread-manager-CcRXasqs.d.ts} +2 -4
  61. package/dist/{thread-manager-HSwyh28L.d.cts → thread-manager-ClwSaUnj.d.cts} +2 -4
  62. package/dist/{thread-manager-c1gPopAG.d.ts → thread-manager-D-7lp1JK.d.ts} +2 -4
  63. package/dist/{thread-manager-wGi-LqIP.d.cts → thread-manager-Y8Ucf0Tf.d.cts} +2 -4
  64. package/dist/{types-C06FwR96.d.cts → types-Bcbiq8iv.d.cts} +162 -44
  65. package/dist/{types-BH_IRryz.d.ts → types-DpHTX-iO.d.ts} +54 -6
  66. package/dist/{types-DNr31FzL.d.ts → types-Dt8-HBBT.d.ts} +162 -44
  67. package/dist/{types-BaOw4hKI.d.cts → types-hFFi-Zd9.d.cts} +54 -6
  68. package/dist/{workflow-CSCkpwAL.d.ts → workflow-Bmf9EtDW.d.ts} +82 -2
  69. package/dist/{workflow-DuvMZ8Vm.d.cts → workflow-Bx9utBwb.d.cts} +82 -2
  70. package/dist/workflow.cjs +188 -37
  71. package/dist/workflow.cjs.map +1 -1
  72. package/dist/workflow.d.cts +2 -2
  73. package/dist/workflow.d.ts +2 -2
  74. package/dist/workflow.js +185 -38
  75. package/dist/workflow.js.map +1 -1
  76. package/package.json +11 -1
  77. package/src/adapters/thread/adapter-id.test.ts +42 -0
  78. package/src/adapters/thread/anthropic/activities.ts +33 -7
  79. package/src/adapters/thread/anthropic/adapter-id.ts +16 -0
  80. package/src/adapters/thread/anthropic/fork-transform.test.ts +291 -0
  81. package/src/adapters/thread/anthropic/index.ts +3 -0
  82. package/src/adapters/thread/anthropic/model-invoker.ts +8 -4
  83. package/src/adapters/thread/anthropic/proxy.ts +3 -2
  84. package/src/adapters/thread/anthropic/thread-manager.ts +27 -4
  85. package/src/adapters/thread/google-genai/activities.ts +33 -7
  86. package/src/adapters/thread/google-genai/adapter-id.ts +16 -0
  87. package/src/adapters/thread/google-genai/fork-transform.test.ts +149 -0
  88. package/src/adapters/thread/google-genai/index.ts +3 -0
  89. package/src/adapters/thread/google-genai/model-invoker.ts +7 -3
  90. package/src/adapters/thread/google-genai/proxy.ts +3 -2
  91. package/src/adapters/thread/google-genai/thread-manager.ts +27 -4
  92. package/src/adapters/thread/index.ts +39 -0
  93. package/src/adapters/thread/langchain/activities.ts +33 -7
  94. package/src/adapters/thread/langchain/adapter-id.ts +16 -0
  95. package/src/adapters/thread/langchain/fork-transform.test.ts +142 -0
  96. package/src/adapters/thread/langchain/index.ts +3 -0
  97. package/src/adapters/thread/langchain/model-invoker.ts +8 -3
  98. package/src/adapters/thread/langchain/proxy.ts +3 -2
  99. package/src/adapters/thread/langchain/thread-manager.ts +27 -4
  100. package/src/lib/lifecycle.ts +3 -1
  101. package/src/lib/model/types.ts +7 -10
  102. package/src/lib/session/session-edge-cases.integration.test.ts +131 -63
  103. package/src/lib/session/session.integration.test.ts +174 -5
  104. package/src/lib/session/session.ts +68 -28
  105. package/src/lib/session/types.ts +60 -9
  106. package/src/lib/state/index.ts +1 -0
  107. package/src/lib/state/manager.integration.test.ts +109 -0
  108. package/src/lib/state/manager.ts +38 -8
  109. package/src/lib/state/types.ts +25 -0
  110. package/src/lib/subagent/handler.ts +124 -11
  111. package/src/lib/subagent/index.ts +5 -1
  112. package/src/lib/subagent/subagent.integration.test.ts +528 -0
  113. package/src/lib/subagent/types.ts +63 -14
  114. package/src/lib/subagent/workflow.ts +29 -2
  115. package/src/lib/thread/index.ts +5 -0
  116. package/src/lib/thread/keys.test.ts +101 -0
  117. package/src/lib/thread/keys.ts +94 -0
  118. package/src/lib/thread/manager.test.ts +139 -0
  119. package/src/lib/thread/manager.ts +92 -14
  120. package/src/lib/thread/proxy.ts +2 -0
  121. package/src/lib/thread/types.ts +60 -6
  122. package/src/lib/tool-router/types.ts +16 -8
  123. package/src/lib/types.ts +12 -0
  124. package/src/workflow.ts +12 -1
  125. package/tsup.config.ts +1 -0
package/dist/index.cjs CHANGED
@@ -444,6 +444,7 @@ function createSubagentTool(subagents) {
444
444
  var childSandboxReadySignal = workflow.defineSignal("childSandboxReady");
445
445
 
446
446
  // src/lib/subagent/handler.ts
447
+ var DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT = "1h";
447
448
  function resolveSandboxConfig(config) {
448
449
  if (!config || config === "none") {
449
450
  return { source: "none", init: "per-call", continuation: "fork" };
@@ -475,17 +476,28 @@ function createSubagentHandler(subagents) {
475
476
  const threadSandboxes = /* @__PURE__ */ new Map();
476
477
  const persistentSandboxes = /* @__PURE__ */ new Map();
477
478
  const persistentSandboxCreating = /* @__PURE__ */ new Set();
479
+ const persistentSandboxCreationError = /* @__PURE__ */ new Map();
478
480
  const lazyCreatorAgent = /* @__PURE__ */ new Map();
481
+ const snapshotBaseCreatorAgent = /* @__PURE__ */ new Map();
479
482
  const threadSnapshots = /* @__PURE__ */ new Map();
480
483
  const persistentBaseSnapshot = /* @__PURE__ */ new Map();
481
484
  const persistentBaseSnapshotCreating = /* @__PURE__ */ new Set();
482
- workflow.setHandler(childSandboxReadySignal, ({ childWorkflowId, sandboxId }) => {
483
- const agentName = lazyCreatorAgent.get(childWorkflowId);
484
- if (agentName && !persistentSandboxes.has(agentName)) {
485
- persistentSandboxes.set(agentName, sandboxId);
486
- lazyCreatorAgent.delete(childWorkflowId);
485
+ const persistentBaseSnapshotCreationError = /* @__PURE__ */ new Map();
486
+ workflow.setHandler(
487
+ childSandboxReadySignal,
488
+ ({ childWorkflowId, sandboxId, baseSnapshot }) => {
489
+ const lazyAgent = lazyCreatorAgent.get(childWorkflowId);
490
+ if (lazyAgent && !persistentSandboxes.has(lazyAgent)) {
491
+ persistentSandboxes.set(lazyAgent, sandboxId);
492
+ lazyCreatorAgent.delete(childWorkflowId);
493
+ }
494
+ const snapAgent = snapshotBaseCreatorAgent.get(childWorkflowId);
495
+ if (snapAgent && baseSnapshot && !persistentBaseSnapshot.has(snapAgent)) {
496
+ persistentBaseSnapshot.set(snapAgent, baseSnapshot);
497
+ snapshotBaseCreatorAgent.delete(childWorkflowId);
498
+ }
487
499
  }
488
- });
500
+ );
489
501
  const handler = async (args, context) => {
490
502
  const config = subagents.find((s) => s.agentName === args.subagent);
491
503
  if (!config) {
@@ -541,8 +553,20 @@ function createSubagentHandler(subagents) {
541
553
  baseSnap = persistentBaseSnapshot.get(config.agentName);
542
554
  if (!baseSnap) {
543
555
  if (persistentBaseSnapshotCreating.has(config.agentName)) {
544
- await workflow.condition(() => persistentBaseSnapshot.has(config.agentName));
556
+ await workflow.condition(
557
+ () => persistentBaseSnapshot.has(config.agentName) || persistentBaseSnapshotCreationError.has(config.agentName) || !persistentBaseSnapshotCreating.has(config.agentName)
558
+ );
559
+ const creatorErr = persistentBaseSnapshotCreationError.get(
560
+ config.agentName
561
+ );
562
+ if (creatorErr !== void 0) {
563
+ throw creatorErr;
564
+ }
545
565
  baseSnap = persistentBaseSnapshot.get(config.agentName);
566
+ if (!baseSnap) {
567
+ persistentBaseSnapshotCreating.add(config.agentName);
568
+ isSnapshotBaseCreator = true;
569
+ }
546
570
  } else {
547
571
  persistentBaseSnapshotCreating.add(config.agentName);
548
572
  isSnapshotBaseCreator = true;
@@ -560,8 +584,20 @@ function createSubagentHandler(subagents) {
560
584
  baseSandboxId = persistentSandboxes.get(config.agentName);
561
585
  if (!baseSandboxId) {
562
586
  if (persistentSandboxCreating.has(config.agentName)) {
563
- await workflow.condition(() => persistentSandboxes.has(config.agentName));
587
+ await workflow.condition(
588
+ () => persistentSandboxes.has(config.agentName) || persistentSandboxCreationError.has(config.agentName) || !persistentSandboxCreating.has(config.agentName)
589
+ );
590
+ const creatorErr = persistentSandboxCreationError.get(
591
+ config.agentName
592
+ );
593
+ if (creatorErr !== void 0) {
594
+ throw creatorErr;
595
+ }
564
596
  baseSandboxId = persistentSandboxes.get(config.agentName);
597
+ if (!baseSandboxId) {
598
+ persistentSandboxCreating.add(config.agentName);
599
+ isLazyCreator = true;
600
+ }
565
601
  } else {
566
602
  persistentSandboxCreating.add(config.agentName);
567
603
  isLazyCreator = true;
@@ -590,6 +626,12 @@ function createSubagentHandler(subagents) {
590
626
  };
591
627
  const resolvedContext = config.context === void 0 ? void 0 : typeof config.context === "function" ? config.context() : config.context;
592
628
  const childOpts = {
629
+ // Apply a bounded run timeout by default so a child workflow that
630
+ // fails to initialize or otherwise never reaches a terminal state
631
+ // cannot hang the parent's `Subagent` tool call forever. Callers can
632
+ // raise, lower, or disable it via `workflowOptions.workflowRunTimeout`.
633
+ workflowRunTimeout: DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT,
634
+ ...config.workflowOptions ?? {},
593
635
  workflowId: childWorkflowId,
594
636
  args: resolvedContext === void 0 ? [args.prompt, workflowInput] : [args.prompt, workflowInput, resolvedContext],
595
637
  taskQueue: config.taskQueue ?? parentTaskQueue
@@ -597,13 +639,39 @@ function createSubagentHandler(subagents) {
597
639
  if (isLazyCreator) {
598
640
  lazyCreatorAgent.set(childWorkflowId, config.agentName);
599
641
  }
642
+ if (isSnapshotBaseCreator) {
643
+ snapshotBaseCreatorAgent.set(childWorkflowId, config.agentName);
644
+ }
600
645
  workflow.log.info("subagent spawned", {
601
646
  subagent: config.agentName,
602
647
  childWorkflowId,
603
648
  threadMode,
604
649
  sandboxSource: sandboxCfg.source
605
650
  });
606
- const childResult = await workflow.executeChild(config.workflow, childOpts);
651
+ let childResult;
652
+ try {
653
+ childResult = await workflow.executeChild(
654
+ config.workflow,
655
+ childOpts
656
+ );
657
+ } catch (err) {
658
+ workflow.log.warn("subagent failed", {
659
+ subagent: config.agentName,
660
+ childWorkflowId,
661
+ error: err instanceof Error ? err.message : String(err)
662
+ });
663
+ if (isLazyCreator) {
664
+ persistentSandboxCreating.delete(config.agentName);
665
+ persistentSandboxCreationError.set(config.agentName, err);
666
+ lazyCreatorAgent.delete(childWorkflowId);
667
+ }
668
+ if (isSnapshotBaseCreator) {
669
+ persistentBaseSnapshotCreating.delete(config.agentName);
670
+ persistentBaseSnapshotCreationError.set(config.agentName, err);
671
+ snapshotBaseCreatorAgent.delete(childWorkflowId);
672
+ }
673
+ throw err;
674
+ }
607
675
  const effectiveShutdown = sandboxShutdownOverride ?? sandboxCfg.shutdown ?? "destroy";
608
676
  workflow.log.info("subagent completed", {
609
677
  subagent: config.agentName,
@@ -647,10 +715,13 @@ function createSubagentHandler(subagents) {
647
715
  }
648
716
  if (isLazyCreator) {
649
717
  persistentSandboxCreating.delete(config.agentName);
718
+ persistentSandboxCreationError.delete(config.agentName);
650
719
  lazyCreatorAgent.delete(childWorkflowId);
651
720
  }
652
721
  if (isSnapshotBaseCreator) {
653
722
  persistentBaseSnapshotCreating.delete(config.agentName);
723
+ persistentBaseSnapshotCreationError.delete(config.agentName);
724
+ snapshotBaseCreatorAgent.delete(childWorkflowId);
654
725
  }
655
726
  if (!toolResponse) {
656
727
  return {
@@ -893,6 +964,7 @@ async function createSession({
893
964
  sandbox: sandboxInit,
894
965
  sandboxShutdown = "destroy",
895
966
  onSandboxReady,
967
+ onSessionExit,
896
968
  virtualFs: virtualFsConfig,
897
969
  virtualFsOps
898
970
  }) {
@@ -918,7 +990,8 @@ async function createSession({
918
990
  appendSystemMessage,
919
991
  appendAgentMessage,
920
992
  forkThread,
921
- truncateThread
993
+ loadThreadState,
994
+ saveThreadState
922
995
  } = threadOps;
923
996
  const plugins = [];
924
997
  let destroySubagentSandboxes;
@@ -1046,7 +1119,10 @@ async function createSession({
1046
1119
  baseSnapshot = await sandboxOps.snapshotSandbox(sandboxId);
1047
1120
  }
1048
1121
  if (sandboxId && sandboxOwned && onSandboxReady) {
1049
- onSandboxReady(sandboxId);
1122
+ onSandboxReady({
1123
+ sandboxId,
1124
+ ...baseSnapshot && { baseSnapshot }
1125
+ });
1050
1126
  }
1051
1127
  if (virtualFsConfig) {
1052
1128
  if (!virtualFsOps) {
@@ -1089,9 +1165,20 @@ async function createSession({
1089
1165
  });
1090
1166
  const sessionStartMs = Date.now();
1091
1167
  const systemPrompt = stateManager.getSystemPrompt();
1168
+ const rehydrateFromSlice = (slice) => {
1169
+ stateManager.mergeUpdate({
1170
+ tasks: new Map(slice.tasks),
1171
+ ...slice.custom
1172
+ });
1173
+ };
1092
1174
  if (threadMode === "fork" && sourceThreadId) {
1093
1175
  await forkThread(sourceThreadId, threadId, threadKey);
1094
- } else if (threadMode === "continue") ; else {
1176
+ const forkedSlice = await loadThreadState(threadId, threadKey);
1177
+ if (forkedSlice) rehydrateFromSlice(forkedSlice);
1178
+ } else if (threadMode === "continue") {
1179
+ const continuedSlice = await loadThreadState(threadId, threadKey);
1180
+ if (continuedSlice) rehydrateFromSlice(continuedSlice);
1181
+ } else {
1095
1182
  if (appendSystemPrompt) {
1096
1183
  if (systemPrompt == null || typeof systemPrompt === "string" && systemPrompt.trim() === "") {
1097
1184
  throw workflow.ApplicationFailure.create({
@@ -1113,24 +1200,21 @@ async function createSession({
1113
1200
  let exitReason = "completed";
1114
1201
  let finalMessage = null;
1115
1202
  try {
1203
+ let assistantId;
1116
1204
  while (stateManager.isRunning() && !stateManager.isTerminal() && stateManager.getTurns() < maxTurns) {
1117
1205
  stateManager.incrementTurns();
1118
1206
  const currentTurn = stateManager.getTurns();
1119
1207
  workflow.log.debug("turn started", { agentName, threadId, turn: currentTurn });
1120
1208
  stateManager.setTools(toolRouter.getToolDefinitions());
1121
- const {
1122
- message,
1123
- rawToolCalls,
1124
- usage,
1125
- threadLengthAtCall
1126
- } = await runAgent({
1209
+ assistantId ??= workflow.uuid4();
1210
+ const { message, rawToolCalls, usage } = await runAgent({
1127
1211
  threadId,
1128
1212
  threadKey,
1129
1213
  agentName,
1130
- metadata
1214
+ metadata,
1215
+ assistantMessageId: assistantId
1131
1216
  });
1132
- const preAssistantLength = threadLengthAtCall;
1133
- await appendAgentMessage(threadId, workflow.uuid4(), message, threadKey);
1217
+ await appendAgentMessage(threadId, assistantId, message, threadKey);
1134
1218
  if (usage) {
1135
1219
  stateManager.updateUsage(usage);
1136
1220
  }
@@ -1184,15 +1268,9 @@ async function createSession({
1184
1268
  toolCallId: rewind.toolCallId,
1185
1269
  toolName: rewind.toolName
1186
1270
  });
1187
- if (preAssistantLength === void 0) {
1188
- throw workflow.ApplicationFailure.create({
1189
- message: "Rewind requested but runAgent did not report `threadLengthAtCall`; the adapter must populate it to support rewinds.",
1190
- nonRetryable: true
1191
- });
1192
- }
1193
- await truncateThread(threadId, preAssistantLength, threadKey);
1194
1271
  continue;
1195
1272
  }
1273
+ assistantId = void 0;
1196
1274
  if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
1197
1275
  const conditionMet = await workflow.condition(
1198
1276
  () => stateManager.getStatus() === "RUNNING",
@@ -1225,6 +1303,19 @@ async function createSession({
1225
1303
  });
1226
1304
  throw workflow.ApplicationFailure.fromError(error);
1227
1305
  } finally {
1306
+ try {
1307
+ await saveThreadState(
1308
+ threadId,
1309
+ stateManager.getPersistedSlice(),
1310
+ threadKey
1311
+ );
1312
+ } catch (persistError) {
1313
+ workflow.log.warn("failed to persist thread state", {
1314
+ agentName,
1315
+ threadId,
1316
+ error: persistError instanceof Error ? persistError.message : String(persistError)
1317
+ });
1318
+ }
1228
1319
  await callSessionEnd(exitReason, stateManager.getTurns());
1229
1320
  if (sandboxOwned && sandboxId && sandboxOps) {
1230
1321
  switch (sandboxShutdown) {
@@ -1261,6 +1352,12 @@ async function createSession({
1261
1352
  ...baseSnapshot && { hasBaseSnapshot: true },
1262
1353
  ...exitSnapshot && { hasExitSnapshot: true }
1263
1354
  });
1355
+ if (onSessionExit) {
1356
+ onSessionExit({
1357
+ ...sandboxId && { sandboxId },
1358
+ ...exitSnapshot && { snapshot: exitSnapshot }
1359
+ });
1360
+ }
1264
1361
  return {
1265
1362
  threadId,
1266
1363
  finalMessage,
@@ -1289,6 +1386,18 @@ function defineWorkflow(config, fn) {
1289
1386
  return workflow;
1290
1387
  }
1291
1388
 
1389
+ // src/lib/thread/keys.ts
1390
+ var THREAD_TTL_SECONDS = 60 * 60 * 24 * 90;
1391
+ function getThreadListKey(threadKey, threadId) {
1392
+ return `${threadKey}:thread:${threadId}`;
1393
+ }
1394
+ function getThreadMetaKey(threadKey, threadId) {
1395
+ return `${threadKey}:meta:thread:${threadId}`;
1396
+ }
1397
+ function getThreadStateKey(threadKey, threadId) {
1398
+ return `${threadKey}:state:thread:${threadId}`;
1399
+ }
1400
+
1292
1401
  // src/lib/types.ts
1293
1402
  function isTerminalStatus(status) {
1294
1403
  return status === "COMPLETED" || status === "FAILED" || status === "CANCELLED";
@@ -1308,11 +1417,19 @@ function createAgentStateManager({
1308
1417
  let systemPrompt = initialState?.systemPrompt;
1309
1418
  const tasks = new Map(initialState?.tasks);
1310
1419
  const {
1311
- status: _,
1312
- version: __,
1313
- turns: ___,
1314
- tasks: ____,
1315
- tools: _____,
1420
+ status: _status,
1421
+ version: _version,
1422
+ turns: _turns,
1423
+ tasks: _tasks,
1424
+ tools: _tools,
1425
+ systemPrompt: _systemPrompt,
1426
+ fileTree: _fileTree,
1427
+ inlineFiles: _inlineFiles,
1428
+ virtualFsCtx: _virtualFsCtx,
1429
+ totalInputTokens: _totalInputTokens,
1430
+ totalOutputTokens: _totalOutputTokens,
1431
+ cachedWriteTokens: _cachedWriteTokens,
1432
+ cachedReadTokens: _cachedReadTokens,
1316
1433
  ...custom
1317
1434
  } = initialState ?? {};
1318
1435
  const customState = custom;
@@ -1392,7 +1509,14 @@ function createAgentStateManager({
1392
1509
  version++;
1393
1510
  },
1394
1511
  mergeUpdate(update) {
1395
- Object.assign(customState, update);
1512
+ const { tasks: nextTasks, ...rest } = update;
1513
+ if (nextTasks) {
1514
+ tasks.clear();
1515
+ for (const [id, task] of nextTasks) {
1516
+ tasks.set(id, task);
1517
+ }
1518
+ }
1519
+ Object.assign(customState, rest);
1396
1520
  version++;
1397
1521
  },
1398
1522
  getCurrentState() {
@@ -1430,6 +1554,12 @@ function createAgentStateManager({
1430
1554
  }
1431
1555
  return deleted;
1432
1556
  },
1557
+ getPersistedSlice() {
1558
+ return {
1559
+ tasks: Array.from(tasks.entries()),
1560
+ custom: { ...customState }
1561
+ };
1562
+ },
1433
1563
  updateUsage(usage) {
1434
1564
  totalInputTokens += usage.inputTokens ?? 0;
1435
1565
  totalOutputTokens += usage.outputTokens ?? 0;
@@ -1551,22 +1681,42 @@ function defineSubagentWorkflow(config, fn) {
1551
1681
  });
1552
1682
  }
1553
1683
  const parentHandle = workflow.getExternalWorkflowHandle(parent.workflowId);
1684
+ let capturedSandboxId;
1685
+ let capturedSnapshot;
1686
+ let capturedBaseSnapshot;
1687
+ let capturedThreadId;
1554
1688
  const sessionInput = {
1555
1689
  agentName: config.name,
1556
1690
  sandboxShutdown: effectiveShutdown,
1557
1691
  ...workflowInput.thread && { thread: workflowInput.thread },
1558
1692
  ...workflowInput.sandbox && { sandbox: workflowInput.sandbox },
1559
- onSandboxReady: (sandboxId) => {
1693
+ onSandboxReady: ({ sandboxId, baseSnapshot }) => {
1694
+ capturedBaseSnapshot = baseSnapshot;
1560
1695
  const isReuse = workflowInput.sandbox?.mode === "continue";
1561
1696
  if (!isReuse) {
1562
1697
  void parentHandle.signal(childSandboxReadySignal, {
1563
1698
  childWorkflowId: workflow.workflowInfo().workflowId,
1564
- sandboxId
1699
+ sandboxId,
1700
+ ...baseSnapshot && { baseSnapshot }
1565
1701
  });
1566
1702
  }
1703
+ },
1704
+ onSessionExit: ({ sandboxId, snapshot, threadId }) => {
1705
+ capturedSandboxId = sandboxId;
1706
+ capturedSnapshot = snapshot;
1707
+ capturedThreadId = threadId;
1708
+ }
1709
+ };
1710
+ const result = await fn(prompt, sessionInput, context ?? {});
1711
+ return {
1712
+ ...result,
1713
+ ...capturedThreadId !== void 0 && { threadId: capturedThreadId },
1714
+ ...capturedSandboxId !== void 0 && { sandboxId: capturedSandboxId },
1715
+ ...capturedSnapshot !== void 0 && { snapshot: capturedSnapshot },
1716
+ ...capturedBaseSnapshot !== void 0 && {
1717
+ baseSnapshot: capturedBaseSnapshot
1567
1718
  }
1568
1719
  };
1569
- return fn(prompt, sessionInput, context ?? {});
1570
1720
  };
1571
1721
  Object.defineProperty(workflow$1, "name", { value: config.name });
1572
1722
  return Object.assign(workflow$1, {
@@ -2336,7 +2486,6 @@ var FileSystemSkillProvider = class {
2336
2486
  };
2337
2487
 
2338
2488
  // src/lib/thread/manager.ts
2339
- var THREAD_TTL_SECONDS = 60 * 60 * 24 * 90;
2340
2489
  var APPEND_IDEMPOTENT_SCRIPT = `
2341
2490
  if redis.call('EXISTS', KEYS[1]) == 1 then
2342
2491
  return 0
@@ -2348,8 +2497,8 @@ redis.call('EXPIRE', KEYS[2], tonumber(ARGV[1]))
2348
2497
  redis.call('SET', KEYS[1], '1', 'EX', tonumber(ARGV[1]))
2349
2498
  return 1
2350
2499
  `;
2351
- function getThreadKey(threadId, key) {
2352
- return `${key}:thread:${threadId}`;
2500
+ function getDedupKey(threadId, id) {
2501
+ return `dedup:${id}:thread:${threadId}`;
2353
2502
  }
2354
2503
  function createThreadManager(config) {
2355
2504
  const {
@@ -2360,8 +2509,9 @@ function createThreadManager(config) {
2360
2509
  deserialize = (raw) => JSON.parse(raw),
2361
2510
  idOf
2362
2511
  } = config;
2363
- const redisKey = getThreadKey(threadId, key);
2364
- const metaKey = getThreadKey(threadId, `${key}:meta`);
2512
+ const redisKey = getThreadListKey(key, threadId);
2513
+ const metaKey = getThreadMetaKey(key, threadId);
2514
+ const stateKey = getThreadStateKey(key, threadId);
2365
2515
  async function assertThreadExists() {
2366
2516
  const exists = await redis.exists(metaKey);
2367
2517
  if (!exists) {
@@ -2383,7 +2533,7 @@ function createThreadManager(config) {
2383
2533
  await assertThreadExists();
2384
2534
  if (idOf) {
2385
2535
  const dedupId = messages.map(idOf).join(":");
2386
- const dedupKey = getThreadKey(threadId, `dedup:${dedupId}`);
2536
+ const dedupKey = getDedupKey(threadId, dedupId);
2387
2537
  await redis.eval(
2388
2538
  APPEND_IDEMPOTENT_SCRIPT,
2389
2539
  2,
@@ -2400,34 +2550,98 @@ function createThreadManager(config) {
2400
2550
  async fork(newThreadId) {
2401
2551
  await assertThreadExists();
2402
2552
  const data = await redis.lrange(redisKey, 0, -1);
2553
+ const stateRaw = await redis.get(stateKey);
2403
2554
  const forked = createThreadManager({
2404
2555
  ...config,
2405
2556
  threadId: newThreadId
2406
2557
  });
2407
2558
  await forked.initialize();
2408
2559
  if (data.length > 0) {
2409
- const newKey = getThreadKey(newThreadId, key);
2560
+ const newKey = getThreadListKey(key, newThreadId);
2410
2561
  await redis.rpush(newKey, ...data);
2411
2562
  await redis.expire(newKey, THREAD_TTL_SECONDS);
2412
2563
  }
2564
+ if (stateRaw != null) {
2565
+ const newStateKey = getThreadStateKey(key, newThreadId);
2566
+ await redis.set(newStateKey, stateRaw, "EX", THREAD_TTL_SECONDS);
2567
+ }
2413
2568
  return forked;
2414
2569
  },
2570
+ async replaceAll(messages) {
2571
+ await assertThreadExists();
2572
+ if (!idOf) {
2573
+ throw new Error(
2574
+ "replaceAll requires the thread manager to be configured with `idOf`"
2575
+ );
2576
+ }
2577
+ const existing = await redis.lrange(redisKey, 0, -1);
2578
+ const existingIds = existing.map((raw) => idOf(deserialize(raw))).filter((id) => typeof id === "string");
2579
+ await redis.del(redisKey);
2580
+ if (existingIds.length > 0) {
2581
+ await redis.del(
2582
+ ...existingIds.map((id) => getDedupKey(threadId, id))
2583
+ );
2584
+ }
2585
+ if (messages.length > 0) {
2586
+ await redis.rpush(redisKey, ...messages.map(serialize));
2587
+ await redis.expire(redisKey, THREAD_TTL_SECONDS);
2588
+ }
2589
+ await redis.expire(metaKey, THREAD_TTL_SECONDS);
2590
+ },
2415
2591
  async delete() {
2416
- await redis.del(redisKey, metaKey);
2592
+ await redis.del(redisKey, metaKey, stateKey);
2593
+ },
2594
+ async loadState() {
2595
+ const raw = await redis.get(stateKey);
2596
+ if (raw == null) return null;
2597
+ return JSON.parse(raw);
2598
+ },
2599
+ async saveState(state) {
2600
+ await assertThreadExists();
2601
+ await redis.set(
2602
+ stateKey,
2603
+ JSON.stringify(state),
2604
+ "EX",
2605
+ THREAD_TTL_SECONDS
2606
+ );
2607
+ },
2608
+ async deleteState() {
2609
+ await redis.del(stateKey);
2417
2610
  },
2418
2611
  async length() {
2419
2612
  await assertThreadExists();
2420
2613
  return redis.llen(redisKey);
2421
2614
  },
2422
- async truncate(length) {
2615
+ async truncateFromId(messageId) {
2423
2616
  await assertThreadExists();
2424
- if (length <= 0) {
2617
+ if (!idOf) {
2618
+ throw new Error(
2619
+ "truncateFromId requires the thread manager to be configured with `idOf`"
2620
+ );
2621
+ }
2622
+ const data = await redis.lrange(redisKey, 0, -1);
2623
+ let idx = -1;
2624
+ const removedIds = [];
2625
+ for (let i = 0; i < data.length; i++) {
2626
+ const raw = data[i];
2627
+ if (raw === void 0) continue;
2628
+ const id = idOf(deserialize(raw));
2629
+ if (idx === -1 && id === messageId) idx = i;
2630
+ if (idx !== -1) removedIds.push(id);
2631
+ }
2632
+ if (idx === -1) return;
2633
+ if (idx === 0) {
2425
2634
  await redis.del(redisKey);
2426
2635
  await redis.expire(metaKey, THREAD_TTL_SECONDS);
2427
2636
  } else {
2428
- await redis.ltrim(redisKey, 0, length - 1);
2637
+ await redis.ltrim(redisKey, 0, idx - 1);
2429
2638
  await redis.expire(redisKey, THREAD_TTL_SECONDS);
2430
2639
  }
2640
+ if (removedIds.length > 0) {
2641
+ await redis.del(
2642
+ ...removedIds.map((id) => getDedupKey(threadId, id))
2643
+ );
2644
+ }
2431
2645
  }
2432
2646
  };
2433
2647
  }
@@ -3242,11 +3456,13 @@ var toTree = async (fs, opts = {}) => {
3242
3456
  return base + subtree;
3243
3457
  };
3244
3458
 
3459
+ exports.DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT = DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT;
3245
3460
  exports.FileSystemSkillProvider = FileSystemSkillProvider;
3246
3461
  exports.NodeFsSandboxFileSystem = NodeFsSandboxFileSystem;
3247
3462
  exports.SandboxManager = SandboxManager;
3248
3463
  exports.SandboxNotFoundError = SandboxNotFoundError;
3249
3464
  exports.SandboxNotSupportedError = SandboxNotSupportedError;
3465
+ exports.THREAD_TTL_SECONDS = THREAD_TTL_SECONDS;
3250
3466
  exports.VirtualFileSystem = VirtualFileSystem;
3251
3467
  exports.applyVirtualTreeMutations = applyVirtualTreeMutations;
3252
3468
  exports.askUserQuestionTool = askUserQuestionTool;
@@ -3278,6 +3494,8 @@ exports.filesWithMimeType = filesWithMimeType;
3278
3494
  exports.formatVirtualFileTree = formatVirtualFileTree;
3279
3495
  exports.getActivityContext = getActivityContext;
3280
3496
  exports.getShortId = getShortId;
3497
+ exports.getThreadListKey = getThreadListKey;
3498
+ exports.getThreadMetaKey = getThreadMetaKey;
3281
3499
  exports.globHandler = globHandler;
3282
3500
  exports.globTool = globTool;
3283
3501
  exports.grepTool = grepTool;