zeitlich 0.2.36 → 0.2.38

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 (204) hide show
  1. package/README.md +146 -92
  2. package/dist/{activities-BVI2lTwr.d.ts → activities-BKhMtKDd.d.ts} +4 -2
  3. package/dist/{activities-hd4aNnZE.d.cts → activities-CDcwkRZs.d.cts} +4 -2
  4. package/dist/adapters/sandbox/bedrock/index.cjs +17 -14
  5. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  6. package/dist/adapters/sandbox/bedrock/index.d.cts +7 -6
  7. package/dist/adapters/sandbox/bedrock/index.d.ts +7 -6
  8. package/dist/adapters/sandbox/bedrock/index.js +17 -14
  9. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  10. package/dist/adapters/sandbox/bedrock/workflow.cjs +2 -0
  11. package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -1
  12. package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
  13. package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
  14. package/dist/adapters/sandbox/bedrock/workflow.js +2 -0
  15. package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
  16. package/dist/adapters/sandbox/daytona/index.cjs +11 -3
  17. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  18. package/dist/adapters/sandbox/daytona/index.d.cts +5 -4
  19. package/dist/adapters/sandbox/daytona/index.d.ts +5 -4
  20. package/dist/adapters/sandbox/daytona/index.js +11 -3
  21. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  22. package/dist/adapters/sandbox/daytona/workflow.cjs +2 -0
  23. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  24. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  25. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  26. package/dist/adapters/sandbox/daytona/workflow.js +2 -0
  27. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  28. package/dist/adapters/sandbox/e2b/index.cjs +73 -12
  29. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  30. package/dist/adapters/sandbox/e2b/index.d.cts +26 -4
  31. package/dist/adapters/sandbox/e2b/index.d.ts +26 -4
  32. package/dist/adapters/sandbox/e2b/index.js +73 -12
  33. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  34. package/dist/adapters/sandbox/e2b/workflow.cjs +2 -0
  35. package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
  36. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  37. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  38. package/dist/adapters/sandbox/e2b/workflow.js +2 -0
  39. package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
  40. package/dist/adapters/sandbox/inmemory/index.cjs +8 -3
  41. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  42. package/dist/adapters/sandbox/inmemory/index.d.cts +5 -4
  43. package/dist/adapters/sandbox/inmemory/index.d.ts +5 -4
  44. package/dist/adapters/sandbox/inmemory/index.js +8 -3
  45. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  46. package/dist/adapters/sandbox/inmemory/workflow.cjs +2 -0
  47. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  48. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  49. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  50. package/dist/adapters/sandbox/inmemory/workflow.js +2 -0
  51. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  52. package/dist/adapters/thread/anthropic/index.cjs +94 -39
  53. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  54. package/dist/adapters/thread/anthropic/index.d.cts +5 -5
  55. package/dist/adapters/thread/anthropic/index.d.ts +5 -5
  56. package/dist/adapters/thread/anthropic/index.js +94 -39
  57. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  58. package/dist/adapters/thread/anthropic/workflow.cjs +7 -2
  59. package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
  60. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  61. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  62. package/dist/adapters/thread/anthropic/workflow.js +7 -2
  63. package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
  64. package/dist/adapters/thread/google-genai/index.cjs +77 -28
  65. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  66. package/dist/adapters/thread/google-genai/index.d.cts +5 -5
  67. package/dist/adapters/thread/google-genai/index.d.ts +5 -5
  68. package/dist/adapters/thread/google-genai/index.js +77 -28
  69. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  70. package/dist/adapters/thread/google-genai/workflow.cjs +7 -2
  71. package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
  72. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
  73. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
  74. package/dist/adapters/thread/google-genai/workflow.js +7 -2
  75. package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
  76. package/dist/adapters/thread/langchain/index.cjs +57 -10
  77. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  78. package/dist/adapters/thread/langchain/index.d.cts +5 -5
  79. package/dist/adapters/thread/langchain/index.d.ts +5 -5
  80. package/dist/adapters/thread/langchain/index.js +57 -10
  81. package/dist/adapters/thread/langchain/index.js.map +1 -1
  82. package/dist/adapters/thread/langchain/workflow.cjs +7 -2
  83. package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
  84. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  85. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  86. package/dist/adapters/thread/langchain/workflow.js +7 -2
  87. package/dist/adapters/thread/langchain/workflow.js.map +1 -1
  88. package/dist/index.cjs +322 -146
  89. package/dist/index.cjs.map +1 -1
  90. package/dist/index.d.cts +20 -14
  91. package/dist/index.d.ts +20 -14
  92. package/dist/index.js +323 -147
  93. package/dist/index.js.map +1 -1
  94. package/dist/{proxy-BjdFGPTm.d.ts → proxy-CUlKSvZS.d.ts} +1 -1
  95. package/dist/{proxy-7RnVaPdJ.d.cts → proxy-D_3x7RN4.d.cts} +1 -1
  96. package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-CVu7o2cs.d.ts} +4 -2
  97. package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-HSwyh28L.d.cts} +4 -2
  98. package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-c1gPopAG.d.ts} +4 -2
  99. package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-wGi-LqIP.d.cts} +4 -2
  100. package/dist/{types-Mc_4BCfT.d.cts → types-BH_IRryz.d.ts} +10 -1
  101. package/dist/{types-yiXmqedU.d.ts → types-BaOw4hKI.d.cts} +10 -1
  102. package/dist/{types-DQ1l_gXL.d.cts → types-C06FwR96.d.cts} +121 -17
  103. package/dist/{types-wiGLvxWf.d.ts → types-DAsQ21Rt.d.ts} +1 -1
  104. package/dist/{types-CADc5V_P.d.ts → types-DNr31FzL.d.ts} +121 -17
  105. package/dist/{types-CBH54cwr.d.cts → types-lm8tMNJQ.d.cts} +1 -1
  106. package/dist/{types-DxCpFNv_.d.cts → types-yx0LzPGn.d.cts} +44 -5
  107. package/dist/{types-DxCpFNv_.d.ts → types-yx0LzPGn.d.ts} +44 -5
  108. package/dist/{workflow-DhtWRovz.d.cts → workflow-CSCkpwAL.d.ts} +2 -2
  109. package/dist/{workflow-P2pTSfKu.d.ts → workflow-DuvMZ8Vm.d.cts} +2 -2
  110. package/dist/workflow.cjs +274 -130
  111. package/dist/workflow.cjs.map +1 -1
  112. package/dist/workflow.d.cts +3 -3
  113. package/dist/workflow.d.ts +3 -3
  114. package/dist/workflow.js +275 -131
  115. package/dist/workflow.js.map +1 -1
  116. package/package.json +2 -2
  117. package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
  118. package/src/adapters/sandbox/bedrock/index.ts +22 -11
  119. package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
  120. package/src/adapters/sandbox/daytona/index.ts +18 -3
  121. package/src/adapters/sandbox/daytona/proxy.ts +2 -0
  122. package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
  123. package/src/adapters/sandbox/e2b/index.ts +87 -14
  124. package/src/adapters/sandbox/e2b/proxy.ts +2 -0
  125. package/src/adapters/sandbox/e2b/types.ts +16 -0
  126. package/src/adapters/sandbox/inmemory/index.ts +17 -3
  127. package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
  128. package/src/adapters/thread/anthropic/activities.ts +58 -26
  129. package/src/adapters/thread/anthropic/model-invoker.ts +18 -7
  130. package/src/adapters/thread/anthropic/proxy.ts +6 -2
  131. package/src/adapters/thread/anthropic/thread-manager.test.ts +26 -7
  132. package/src/adapters/thread/anthropic/thread-manager.ts +63 -46
  133. package/src/adapters/thread/google-genai/activities.ts +20 -2
  134. package/src/adapters/thread/google-genai/model-invoker.ts +27 -7
  135. package/src/adapters/thread/google-genai/proxy.ts +6 -2
  136. package/src/adapters/thread/google-genai/thread-manager.test.ts +13 -3
  137. package/src/adapters/thread/google-genai/thread-manager.ts +57 -33
  138. package/src/adapters/thread/langchain/activities.ts +55 -24
  139. package/src/adapters/thread/langchain/hooks.test.ts +36 -49
  140. package/src/adapters/thread/langchain/hooks.ts +18 -5
  141. package/src/adapters/thread/langchain/model-invoker.ts +5 -4
  142. package/src/adapters/thread/langchain/proxy.ts +6 -2
  143. package/src/adapters/thread/langchain/thread-manager.test.ts +5 -1
  144. package/src/adapters/thread/langchain/thread-manager.ts +23 -9
  145. package/src/index.ts +4 -1
  146. package/src/lib/activity.ts +16 -6
  147. package/src/lib/hooks/types.ts +6 -6
  148. package/src/lib/lifecycle.ts +18 -3
  149. package/src/lib/model/proxy.ts +2 -2
  150. package/src/lib/model/types.ts +10 -0
  151. package/src/lib/observability/hooks.ts +4 -5
  152. package/src/lib/observability/index.ts +1 -4
  153. package/src/lib/sandbox/manager.ts +45 -20
  154. package/src/lib/sandbox/node-fs.ts +3 -6
  155. package/src/lib/sandbox/sandbox.test.ts +36 -3
  156. package/src/lib/sandbox/tree.integration.test.ts +10 -3
  157. package/src/lib/sandbox/types.ts +60 -6
  158. package/src/lib/session/session-edge-cases.integration.test.ts +316 -14
  159. package/src/lib/session/session.integration.test.ts +161 -1
  160. package/src/lib/session/session.ts +106 -21
  161. package/src/lib/session/types.ts +25 -5
  162. package/src/lib/skills/fs-provider.ts +12 -8
  163. package/src/lib/skills/handler.ts +1 -1
  164. package/src/lib/skills/parse.ts +3 -1
  165. package/src/lib/skills/register.ts +1 -3
  166. package/src/lib/skills/skills.integration.test.ts +25 -15
  167. package/src/lib/state/manager.integration.test.ts +12 -2
  168. package/src/lib/subagent/define.ts +1 -1
  169. package/src/lib/subagent/handler.ts +186 -71
  170. package/src/lib/subagent/index.ts +1 -5
  171. package/src/lib/subagent/register.ts +3 -2
  172. package/src/lib/subagent/signals.ts +1 -10
  173. package/src/lib/subagent/subagent.integration.test.ts +526 -248
  174. package/src/lib/subagent/tool.ts +4 -3
  175. package/src/lib/subagent/types.ts +50 -20
  176. package/src/lib/subagent/workflow.ts +9 -49
  177. package/src/lib/thread/id.test.ts +1 -1
  178. package/src/lib/thread/id.ts +1 -2
  179. package/src/lib/thread/manager.ts +18 -0
  180. package/src/lib/thread/proxy.ts +4 -4
  181. package/src/lib/thread/types.ts +20 -3
  182. package/src/lib/tool-router/index.ts +3 -5
  183. package/src/lib/tool-router/router-edge-cases.integration.test.ts +93 -1
  184. package/src/lib/tool-router/router.integration.test.ts +12 -0
  185. package/src/lib/tool-router/router.ts +90 -16
  186. package/src/lib/tool-router/types.ts +45 -4
  187. package/src/lib/tool-router/with-sandbox.ts +19 -5
  188. package/src/lib/virtual-fs/filesystem.ts +1 -1
  189. package/src/lib/virtual-fs/index.ts +5 -1
  190. package/src/lib/virtual-fs/mutations.ts +2 -4
  191. package/src/lib/virtual-fs/queries.ts +9 -5
  192. package/src/lib/virtual-fs/types.ts +4 -1
  193. package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
  194. package/src/lib/workflow.test.ts +7 -4
  195. package/src/lib/workflow.ts +1 -5
  196. package/src/tools/ask-user-question/tool.ts +1 -3
  197. package/src/tools/glob/handler.ts +1 -4
  198. package/src/tools/task-get/handler.ts +4 -5
  199. package/src/tools/task-list/handler.ts +1 -4
  200. package/src/tools/task-update/handler.ts +4 -5
  201. package/src/workflow.ts +22 -7
  202. package/tsup.config.ts +9 -6
  203. package/src/lib/.env +0 -1
  204. package/src/tools/bash/.env +0 -1
@@ -29,6 +29,16 @@ vi.mock("@temporalio/workflow", () => {
29
29
  }
30
30
  }
31
31
 
32
+ class MockCancellationScope {
33
+ cancellable: boolean;
34
+ constructor(opts?: { cancellable?: boolean }) {
35
+ this.cancellable = opts?.cancellable ?? true;
36
+ }
37
+ async run<T>(fn: () => Promise<T>): Promise<T> {
38
+ return fn();
39
+ }
40
+ cancel(): void {}
41
+ }
32
42
  return {
33
43
  proxyActivities: <T>() => ({}) as T,
34
44
  condition: async (fn: () => boolean) => fn(),
@@ -42,7 +52,15 @@ vi.mock("@temporalio/workflow", () => {
42
52
  uuid4: () =>
43
53
  `00000000-0000-0000-0000-${String(++idCounter).padStart(12, "0")}`,
44
54
  ApplicationFailure: MockApplicationFailure,
45
- log: { trace: () => {}, debug: () => {}, info: () => {}, warn: () => {}, error: () => {} },
55
+ CancellationScope: MockCancellationScope,
56
+ isCancellation: (_err: unknown) => false,
57
+ log: {
58
+ trace: () => {},
59
+ debug: () => {},
60
+ info: () => {},
61
+ warn: () => {},
62
+ error: () => {},
63
+ },
46
64
  };
47
65
  });
48
66
 
@@ -55,13 +73,16 @@ type TurnScript = {
55
73
  message: unknown;
56
74
  toolCalls: RawToolCall[];
57
75
  usage?: TokenUsage;
76
+ threadLengthAtCall?: number;
58
77
  };
59
78
 
60
79
  /**
61
80
  * Wraps every method on a ThreadOps object so it also has `.executeWithOptions()`,
62
81
  * matching Temporal's `ActivityInterfaceFor<ThreadOps>` shape.
63
82
  */
64
- function toActivityInterface<TContent = string>(raw: ThreadOps<TContent>): ActivityInterfaceFor<ThreadOps<TContent>> {
83
+ function toActivityInterface<TContent = string>(
84
+ raw: ThreadOps<TContent>
85
+ ): ActivityInterfaceFor<ThreadOps<TContent>> {
65
86
  const result = {} as Record<string, unknown>;
66
87
  for (const [key, fn] of Object.entries(raw)) {
67
88
  const wrapped = (...args: unknown[]) =>
@@ -94,6 +115,9 @@ function createMockThreadOps() {
94
115
  forkThread: async (source, target) => {
95
116
  log.push({ op: "forkThread", args: [source, target] });
96
117
  },
118
+ truncateThread: async (threadId, length) => {
119
+ log.push({ op: "truncateThread", args: [threadId, length] });
120
+ },
97
121
  });
98
122
  return { ops, log };
99
123
  }
@@ -105,12 +129,18 @@ function createScriptedRunAgent(
105
129
  return async () => {
106
130
  const turn = turns[call++];
107
131
  if (!turn) {
108
- return { message: "done", rawToolCalls: [], usage: undefined };
132
+ return {
133
+ message: "done",
134
+ rawToolCalls: [],
135
+ usage: undefined,
136
+ threadLengthAtCall: 0,
137
+ };
109
138
  }
110
139
  return {
111
140
  message: turn.message,
112
141
  rawToolCalls: turn.toolCalls,
113
142
  usage: turn.usage,
143
+ threadLengthAtCall: turn.threadLengthAtCall ?? 0,
114
144
  };
115
145
  };
116
146
  }
@@ -433,6 +463,8 @@ describe("createSession edge cases", () => {
433
463
  createdAt: new Date().toISOString(),
434
464
  }),
435
465
  forkSandbox: async () => "forked-sandbox-id",
466
+ restoreSandbox: async () => "restored-sandbox-id",
467
+ deleteSandboxSnapshot: async () => {},
436
468
  pauseSandbox: async () => {},
437
469
  resumeSandbox: async () => {},
438
470
  };
@@ -476,6 +508,8 @@ describe("createSession edge cases", () => {
476
508
  createdAt: new Date().toISOString(),
477
509
  }),
478
510
  forkSandbox: async () => "forked-sandbox-id",
511
+ restoreSandbox: async () => "restored-sandbox-id",
512
+ deleteSandboxSnapshot: async () => {},
479
513
  pauseSandbox: async () => {},
480
514
  resumeSandbox: async () => {},
481
515
  };
@@ -763,9 +797,16 @@ describe("createSession edge cases", () => {
763
797
  forkThread: async (source, target) => {
764
798
  log.push({ op: "forkThread", args: [source, target] });
765
799
  },
800
+ truncateThread: async (threadId, length) => {
801
+ log.push({ op: "truncateThread", args: [threadId, length] });
802
+ },
766
803
  });
767
804
 
768
- const session = await createSession<Record<string, never>, unknown, TestContent>({
805
+ const session = await createSession<
806
+ Record<string, never>,
807
+ unknown,
808
+ TestContent
809
+ >({
769
810
  agentName: "TestAgent",
770
811
  thread: { mode: "new", threadId: "thread-1" },
771
812
  runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
@@ -940,6 +981,8 @@ describe("createSession edge cases", () => {
940
981
  destroySandbox: async () => {},
941
982
  snapshotSandbox: snapshotSpy,
942
983
  forkSandbox: async () => "forked-sandbox-id",
984
+ restoreSandbox: async () => "restored-sandbox-id",
985
+ deleteSandboxSnapshot: async () => {},
943
986
  pauseSandbox: async () => {},
944
987
  resumeSandbox: async () => {},
945
988
  };
@@ -1040,6 +1083,8 @@ describe("createSession edge cases", () => {
1040
1083
  createdAt: new Date().toISOString(),
1041
1084
  }),
1042
1085
  forkSandbox: async () => "forked-sb",
1086
+ restoreSandbox: async () => "restored-sb",
1087
+ deleteSandboxSnapshot: async () => {},
1043
1088
  };
1044
1089
 
1045
1090
  const session = await createSession({
@@ -1074,6 +1119,8 @@ describe("createSession edge cases", () => {
1074
1119
  createdAt: new Date().toISOString(),
1075
1120
  }),
1076
1121
  forkSandbox: async () => "forked-sb",
1122
+ restoreSandbox: async () => "restored-sb",
1123
+ deleteSandboxSnapshot: async () => {},
1077
1124
  };
1078
1125
 
1079
1126
  const session = await createSession({
@@ -1119,6 +1166,8 @@ describe("createSession edge cases", () => {
1119
1166
  createdAt: new Date().toISOString(),
1120
1167
  }),
1121
1168
  forkSandbox: async () => "forked-sb",
1169
+ restoreSandbox: async () => "restored-sb",
1170
+ deleteSandboxSnapshot: async () => {},
1122
1171
  };
1123
1172
 
1124
1173
  const session = await createSession({
@@ -1166,6 +1215,8 @@ describe("createSession edge cases", () => {
1166
1215
  sandboxLog.push(`fork:${id}`);
1167
1216
  return `forked-from-${id}`;
1168
1217
  },
1218
+ restoreSandbox: async () => "restored-sb",
1219
+ deleteSandboxSnapshot: async () => {},
1169
1220
  };
1170
1221
 
1171
1222
  const session = await createSession({
@@ -1186,7 +1237,9 @@ describe("createSession edge cases", () => {
1186
1237
 
1187
1238
  expect(sandboxLog).toContain("fork:paused-sb-1");
1188
1239
  expect(sandboxLog).not.toContain("create");
1189
- expect((result as { sandboxId?: string }).sandboxId).toBe("forked-from-paused-sb-1");
1240
+ expect((result as { sandboxId?: string }).sandboxId).toBe(
1241
+ "forked-from-paused-sb-1"
1242
+ );
1190
1243
  expect(sandboxLog).toContain("destroy:forked-from-paused-sb-1");
1191
1244
  });
1192
1245
 
@@ -1210,6 +1263,8 @@ describe("createSession edge cases", () => {
1210
1263
  createdAt: new Date().toISOString(),
1211
1264
  }),
1212
1265
  forkSandbox: async () => "forked-sb",
1266
+ restoreSandbox: async () => "restored-sb",
1267
+ deleteSandboxSnapshot: async () => {},
1213
1268
  };
1214
1269
 
1215
1270
  const session = await createSession({
@@ -1253,6 +1308,8 @@ describe("createSession edge cases", () => {
1253
1308
  createdAt: new Date().toISOString(),
1254
1309
  }),
1255
1310
  forkSandbox: async () => "forked-sb",
1311
+ restoreSandbox: async () => "restored-sb",
1312
+ deleteSandboxSnapshot: async () => {},
1256
1313
  };
1257
1314
 
1258
1315
  const session = await createSession({
@@ -1297,6 +1354,8 @@ describe("createSession edge cases", () => {
1297
1354
  createdAt: new Date().toISOString(),
1298
1355
  }),
1299
1356
  forkSandbox: async () => "forked-sb",
1357
+ restoreSandbox: async () => "restored-sb",
1358
+ deleteSandboxSnapshot: async () => {},
1300
1359
  };
1301
1360
 
1302
1361
  const session = await createSession({
@@ -1370,9 +1429,7 @@ describe("createSession edge cases", () => {
1370
1429
 
1371
1430
  const session = await createSession({
1372
1431
  agentName: "TestAgent",
1373
- runAgent: createScriptedRunAgent([
1374
- { message: "done", toolCalls: [] },
1375
- ]),
1432
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1376
1433
  threadOps: ops,
1377
1434
  buildContextMessage: () => "go",
1378
1435
  });
@@ -1413,6 +1470,8 @@ describe("createSession edge cases", () => {
1413
1470
  createdAt: new Date().toISOString(),
1414
1471
  }),
1415
1472
  forkSandbox: async () => "forked-sb",
1473
+ restoreSandbox: async () => "restored-sb",
1474
+ deleteSandboxSnapshot: async () => {},
1416
1475
  };
1417
1476
 
1418
1477
  const session = await createSession({
@@ -1431,9 +1490,7 @@ describe("createSession edge cases", () => {
1431
1490
  initialState: { systemPrompt: "test" },
1432
1491
  });
1433
1492
 
1434
- await expect(session.runSession({ stateManager })).rejects.toThrow(
1435
- "crash"
1436
- );
1493
+ await expect(session.runSession({ stateManager })).rejects.toThrow("crash");
1437
1494
 
1438
1495
  expect(sandboxLog).toContain("pause:sb-err");
1439
1496
  expect(sandboxLog).not.toContain("destroy:sb-err");
@@ -1461,6 +1518,8 @@ describe("createSession edge cases", () => {
1461
1518
  createdAt: new Date().toISOString(),
1462
1519
  }),
1463
1520
  forkSandbox: async () => "forked-sb",
1521
+ restoreSandbox: async () => "restored-sb",
1522
+ deleteSandboxSnapshot: async () => {},
1464
1523
  };
1465
1524
 
1466
1525
  const session = await createSession({
@@ -1503,6 +1562,8 @@ describe("createSession edge cases", () => {
1503
1562
  createdAt: new Date().toISOString(),
1504
1563
  }),
1505
1564
  forkSandbox: async () => "forked-sb",
1565
+ restoreSandbox: async () => "restored-sb",
1566
+ deleteSandboxSnapshot: async () => {},
1506
1567
  };
1507
1568
 
1508
1569
  const session = await createSession({
@@ -1521,9 +1582,7 @@ describe("createSession edge cases", () => {
1521
1582
  initialState: { systemPrompt: "test" },
1522
1583
  });
1523
1584
 
1524
- await expect(session.runSession({ stateManager })).rejects.toThrow(
1525
- "crash"
1526
- );
1585
+ await expect(session.runSession({ stateManager })).rejects.toThrow("crash");
1527
1586
 
1528
1587
  expect(sandboxLog).not.toContain("pause:sb-keep-err");
1529
1588
  expect(sandboxLog).not.toContain("destroy:sb-keep-err");
@@ -1553,6 +1612,8 @@ describe("createSession edge cases", () => {
1553
1612
  createdAt: new Date().toISOString(),
1554
1613
  }),
1555
1614
  forkSandbox: async () => "forked-sb",
1615
+ restoreSandbox: async () => "restored-sb",
1616
+ deleteSandboxSnapshot: async () => {},
1556
1617
  };
1557
1618
 
1558
1619
  const session = await createSession({
@@ -1598,6 +1659,8 @@ describe("createSession edge cases", () => {
1598
1659
  createdAt: new Date().toISOString(),
1599
1660
  }),
1600
1661
  forkSandbox: async () => "forked-sb",
1662
+ restoreSandbox: async () => "restored-sb",
1663
+ deleteSandboxSnapshot: async () => {},
1601
1664
  };
1602
1665
 
1603
1666
  const session = await createSession({
@@ -1621,4 +1684,243 @@ describe("createSession edge cases", () => {
1621
1684
  expect(sandboxLog).not.toContain("pause:kept-sb");
1622
1685
  expect(sandboxLog).not.toContain("destroy:kept-sb");
1623
1686
  });
1687
+
1688
+ // --- Rewind flow: tool requests rewind and turn is retried -------------
1689
+
1690
+ it("rewinds the turn when a tool handler returns rewind:true", async () => {
1691
+ const { ops, log } = createMockThreadOps();
1692
+
1693
+ let rewindAttempts = 0;
1694
+ const rewindTool = defineTool({
1695
+ name: "Rewind" as const,
1696
+ description: "rewinds once then succeeds",
1697
+ schema: z.object({}),
1698
+ handler: async () => {
1699
+ rewindAttempts += 1;
1700
+ if (rewindAttempts === 1) {
1701
+ return {
1702
+ toolResponse: "ignored",
1703
+ data: null,
1704
+ rewind: true,
1705
+ };
1706
+ }
1707
+ return { toolResponse: "ok", data: null };
1708
+ },
1709
+ });
1710
+
1711
+ const session = await createSession({
1712
+ agentName: "TestAgent",
1713
+ thread: { mode: "new", threadId: "thread-1" },
1714
+ runAgent: createScriptedRunAgent([
1715
+ {
1716
+ message: "attempt-1",
1717
+ toolCalls: [{ id: "tc-1", name: "Rewind", args: {} }],
1718
+ },
1719
+ {
1720
+ message: "attempt-2",
1721
+ toolCalls: [{ id: "tc-2", name: "Rewind", args: {} }],
1722
+ },
1723
+ { message: "done", toolCalls: [] },
1724
+ ]),
1725
+ threadOps: ops,
1726
+ tools: { Rewind: rewindTool },
1727
+ buildContextMessage: () => "go",
1728
+ });
1729
+
1730
+ const stateManager = createAgentStateManager({
1731
+ initialState: { systemPrompt: "test" },
1732
+ });
1733
+
1734
+ const result = await session.runSession({ stateManager });
1735
+
1736
+ expect(result.exitReason).toBe("completed");
1737
+ expect(result.finalMessage).toBe("done");
1738
+ expect(rewindAttempts).toBe(2);
1739
+
1740
+ const truncateOps = log.filter((l) => l.op === "truncateThread");
1741
+ expect(truncateOps).toHaveLength(1);
1742
+
1743
+ const noRewindToolResult = log.filter((l) => {
1744
+ if (l.op !== "appendToolResult") return false;
1745
+ const config = l.args[1] as ToolResultConfig;
1746
+ return config.toolCallId === "tc-1";
1747
+ });
1748
+ expect(noRewindToolResult).toHaveLength(0);
1749
+
1750
+ const agentAppends = log.filter((l) => l.op === "appendAgentMessage");
1751
+ expect(agentAppends).toHaveLength(3);
1752
+ });
1753
+
1754
+ it("truncates the thread back to the pre-assistant state so sibling tool results are dropped on rewind", async () => {
1755
+ const { ops, log } = createMockThreadOps();
1756
+
1757
+ let rewindFired = false;
1758
+
1759
+ const siblingTool = defineTool({
1760
+ name: "Sibling" as const,
1761
+ description: "sibling",
1762
+ schema: z.object({}),
1763
+ handler: async () => ({ toolResponse: "sibling-ok", data: null }),
1764
+ });
1765
+
1766
+ const rewindTool = defineTool({
1767
+ name: "Rewind" as const,
1768
+ description: "rewinds",
1769
+ schema: z.object({}),
1770
+ handler: async () => {
1771
+ if (!rewindFired) {
1772
+ rewindFired = true;
1773
+ return { toolResponse: "ignored", data: null, rewind: true };
1774
+ }
1775
+ return { toolResponse: "ok", data: null };
1776
+ },
1777
+ });
1778
+
1779
+ const session = await createSession({
1780
+ agentName: "TestAgent",
1781
+ thread: { mode: "new", threadId: "thread-1" },
1782
+ runAgent: createScriptedRunAgent([
1783
+ {
1784
+ message: "parallel",
1785
+ toolCalls: [
1786
+ { id: "tc-sibling", name: "Sibling", args: {} },
1787
+ { id: "tc-rewind", name: "Rewind", args: {} },
1788
+ ],
1789
+ // Invoker reports 2 stored messages (system + human) at the
1790
+ // moment the LLM was called.
1791
+ threadLengthAtCall: 2,
1792
+ },
1793
+ { message: "done", toolCalls: [], threadLengthAtCall: 2 },
1794
+ ]),
1795
+ threadOps: ops,
1796
+ tools: { Rewind: rewindTool, Sibling: siblingTool },
1797
+ buildContextMessage: () => "go",
1798
+ });
1799
+
1800
+ const stateManager = createAgentStateManager({
1801
+ initialState: { systemPrompt: "test" },
1802
+ });
1803
+
1804
+ const result = await session.runSession({ stateManager });
1805
+
1806
+ expect(result.exitReason).toBe("completed");
1807
+
1808
+ // Exactly one truncate fired — back to the pre-assistant-message
1809
+ // length that runAgent reported.
1810
+ const truncateOps = log.filter((l) => l.op === "truncateThread");
1811
+ expect(truncateOps).toHaveLength(1);
1812
+ const truncateOp = truncateOps[0];
1813
+ if (!truncateOp) throw new Error("expected truncate op");
1814
+ expect(truncateOp.args[1]).toBe(2);
1815
+
1816
+ // Rewinding tool never appends its own result.
1817
+ const rewindResultAppends = log.filter((l) => {
1818
+ if (l.op !== "appendToolResult") return false;
1819
+ const config = l.args[1] as ToolResultConfig;
1820
+ return config.toolCallId === "tc-rewind";
1821
+ });
1822
+ expect(rewindResultAppends).toHaveLength(0);
1823
+
1824
+ // Two assistant messages expected: one from the rewound turn, one from
1825
+ // the successful retry.
1826
+ const agentAppends = log.filter((l) => l.op === "appendAgentMessage");
1827
+ expect(agentAppends).toHaveLength(2);
1828
+ });
1829
+
1830
+ it("does not rewind when the rewinding tool is no longer present after retry", async () => {
1831
+ const { ops, log } = createMockThreadOps();
1832
+
1833
+ let attempts = 0;
1834
+ const rewindOnce = defineTool({
1835
+ name: "RewindOnce" as const,
1836
+ description: "rewinds once",
1837
+ schema: z.object({}),
1838
+ handler: async () => {
1839
+ attempts += 1;
1840
+ if (attempts === 1) {
1841
+ return { toolResponse: "ignored", data: null, rewind: true };
1842
+ }
1843
+ return { toolResponse: "ok", data: null };
1844
+ },
1845
+ });
1846
+
1847
+ const session = await createSession({
1848
+ agentName: "TestAgent",
1849
+ thread: { mode: "new", threadId: "thread-1" },
1850
+ maxTurns: 5,
1851
+ runAgent: createScriptedRunAgent([
1852
+ {
1853
+ message: "call-1",
1854
+ toolCalls: [{ id: "tc-1", name: "RewindOnce", args: {} }],
1855
+ },
1856
+ {
1857
+ message: "call-2",
1858
+ toolCalls: [{ id: "tc-2", name: "RewindOnce", args: {} }],
1859
+ },
1860
+ { message: "done", toolCalls: [] },
1861
+ ]),
1862
+ threadOps: ops,
1863
+ tools: { RewindOnce: rewindOnce },
1864
+ buildContextMessage: () => "go",
1865
+ });
1866
+
1867
+ const stateManager = createAgentStateManager({
1868
+ initialState: { systemPrompt: "test" },
1869
+ });
1870
+
1871
+ const result = await session.runSession({ stateManager });
1872
+
1873
+ expect(result.exitReason).toBe("completed");
1874
+ expect(result.finalMessage).toBe("done");
1875
+ // Each rewind still consumes a turn from the `maxTurns` budget:
1876
+ // turn 1 (rewound) + turn 2 (successful tool call) + turn 3 (done) = 3.
1877
+ expect(result.usage.turns).toBe(3);
1878
+ expect(attempts).toBe(2);
1879
+
1880
+ const truncateOps = log.filter((l) => l.op === "truncateThread");
1881
+ expect(truncateOps).toHaveLength(1);
1882
+ });
1883
+
1884
+ it("bails out with max_turns when a tool keeps requesting rewind", async () => {
1885
+ const { ops, log } = createMockThreadOps();
1886
+
1887
+ let attempts = 0;
1888
+ const alwaysRewind = defineTool({
1889
+ name: "AlwaysRewind" as const,
1890
+ description: "always rewinds",
1891
+ schema: z.object({}),
1892
+ handler: async () => {
1893
+ attempts += 1;
1894
+ return { toolResponse: "ignored", data: null, rewind: true };
1895
+ },
1896
+ });
1897
+
1898
+ const session = await createSession({
1899
+ agentName: "TestAgent",
1900
+ thread: { mode: "new", threadId: "thread-1" },
1901
+ maxTurns: 3,
1902
+ runAgent: createScriptedRunAgent([
1903
+ { message: "t1", toolCalls: [{ id: "tc-1", name: "AlwaysRewind", args: {} }] },
1904
+ { message: "t2", toolCalls: [{ id: "tc-2", name: "AlwaysRewind", args: {} }] },
1905
+ { message: "t3", toolCalls: [{ id: "tc-3", name: "AlwaysRewind", args: {} }] },
1906
+ { message: "t4", toolCalls: [{ id: "tc-4", name: "AlwaysRewind", args: {} }] },
1907
+ ]),
1908
+ threadOps: ops,
1909
+ tools: { AlwaysRewind: alwaysRewind },
1910
+ buildContextMessage: () => "go",
1911
+ });
1912
+
1913
+ const stateManager = createAgentStateManager({
1914
+ initialState: { systemPrompt: "test" },
1915
+ });
1916
+
1917
+ const result = await session.runSession({ stateManager });
1918
+
1919
+ expect(result.exitReason).toBe("max_turns");
1920
+ expect(result.usage.turns).toBe(3);
1921
+ expect(attempts).toBe(3);
1922
+
1923
+ const truncateOps = log.filter((l) => l.op === "truncateThread");
1924
+ expect(truncateOps).toHaveLength(3);
1925
+ });
1624
1926
  });