zeitlich 0.2.22 → 0.2.23

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 (101) hide show
  1. package/README.md +242 -59
  2. package/dist/adapters/sandbox/daytona/index.cjs +4 -1
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
  5. package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
  6. package/dist/adapters/sandbox/daytona/index.js +4 -1
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/daytona/workflow.cjs +1 -0
  9. package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
  10. package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
  11. package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
  12. package/dist/adapters/sandbox/daytona/workflow.js +1 -0
  13. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  14. package/dist/adapters/sandbox/inmemory/index.cjs +16 -2
  15. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
  17. package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
  18. package/dist/adapters/sandbox/inmemory/index.js +16 -2
  19. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  20. package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -0
  21. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
  22. package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
  23. package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
  24. package/dist/adapters/sandbox/inmemory/workflow.js +1 -0
  25. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  26. package/dist/adapters/sandbox/virtual/index.cjs +33 -9
  27. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  28. package/dist/adapters/sandbox/virtual/index.d.cts +6 -5
  29. package/dist/adapters/sandbox/virtual/index.d.ts +6 -5
  30. package/dist/adapters/sandbox/virtual/index.js +33 -9
  31. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  32. package/dist/adapters/sandbox/virtual/workflow.cjs +1 -0
  33. package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -1
  34. package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -3
  35. package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -3
  36. package/dist/adapters/sandbox/virtual/workflow.js +1 -0
  37. package/dist/adapters/sandbox/virtual/workflow.js.map +1 -1
  38. package/dist/adapters/thread/google-genai/index.d.cts +3 -3
  39. package/dist/adapters/thread/google-genai/index.d.ts +3 -3
  40. package/dist/adapters/thread/google-genai/workflow.d.cts +3 -3
  41. package/dist/adapters/thread/google-genai/workflow.d.ts +3 -3
  42. package/dist/adapters/thread/langchain/index.d.cts +3 -3
  43. package/dist/adapters/thread/langchain/index.d.ts +3 -3
  44. package/dist/adapters/thread/langchain/workflow.d.cts +3 -3
  45. package/dist/adapters/thread/langchain/workflow.d.ts +3 -3
  46. package/dist/index.cjs +247 -57
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.cts +9 -8
  49. package/dist/index.d.ts +9 -8
  50. package/dist/index.js +245 -55
  51. package/dist/index.js.map +1 -1
  52. package/dist/{queries-Bw6WEPMw.d.cts → queries-DModcWRy.d.cts} +1 -1
  53. package/dist/{queries-C27raDaB.d.ts → queries-byD0jr1Y.d.ts} +1 -1
  54. package/dist/{types-ClsHhtwL.d.cts → types-B50pBPEV.d.ts} +159 -35
  55. package/dist/{types-YbL7JpEA.d.cts → types-Bll19FZJ.d.cts} +7 -0
  56. package/dist/{types-YbL7JpEA.d.ts → types-Bll19FZJ.d.ts} +7 -0
  57. package/dist/{types-BJ8itUAl.d.cts → types-BuXdFhaZ.d.cts} +6 -6
  58. package/dist/{types-HBosetv3.d.cts → types-ChAMwU3q.d.cts} +2 -0
  59. package/dist/{types-HBosetv3.d.ts → types-ChAMwU3q.d.ts} +2 -0
  60. package/dist/{types-C5bkx6kQ.d.ts → types-DQW8l7pY.d.cts} +159 -35
  61. package/dist/{types-ENYCKFBk.d.ts → types-GZ76HZSj.d.ts} +6 -6
  62. package/dist/workflow.cjs +241 -57
  63. package/dist/workflow.cjs.map +1 -1
  64. package/dist/workflow.d.cts +49 -32
  65. package/dist/workflow.d.ts +49 -32
  66. package/dist/workflow.js +239 -55
  67. package/dist/workflow.js.map +1 -1
  68. package/package.json +2 -2
  69. package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
  70. package/src/adapters/sandbox/daytona/index.ts +4 -0
  71. package/src/adapters/sandbox/daytona/proxy.ts +4 -3
  72. package/src/adapters/sandbox/e2b/index.ts +5 -0
  73. package/src/adapters/sandbox/inmemory/index.ts +24 -4
  74. package/src/adapters/sandbox/inmemory/proxy.ts +2 -2
  75. package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
  76. package/src/adapters/sandbox/virtual/provider.ts +4 -0
  77. package/src/adapters/sandbox/virtual/proxy.ts +1 -0
  78. package/src/adapters/sandbox/virtual/types.ts +9 -4
  79. package/src/lib/lifecycle.ts +57 -0
  80. package/src/lib/sandbox/manager.ts +13 -1
  81. package/src/lib/sandbox/types.ts +13 -4
  82. package/src/lib/session/index.ts +1 -0
  83. package/src/lib/session/session-edge-cases.integration.test.ts +447 -33
  84. package/src/lib/session/session.integration.test.ts +52 -32
  85. package/src/lib/session/session.ts +107 -33
  86. package/src/lib/session/types.ts +55 -16
  87. package/src/lib/subagent/define.ts +5 -4
  88. package/src/lib/subagent/handler.ts +139 -14
  89. package/src/lib/subagent/index.ts +3 -0
  90. package/src/lib/subagent/register.ts +10 -3
  91. package/src/lib/subagent/signals.ts +8 -0
  92. package/src/lib/subagent/subagent.integration.test.ts +853 -150
  93. package/src/lib/subagent/tool.ts +2 -2
  94. package/src/lib/subagent/types.ts +77 -19
  95. package/src/lib/subagent/workflow.ts +83 -12
  96. package/src/lib/tool-router/router.integration.test.ts +137 -4
  97. package/src/lib/tool-router/router.ts +13 -3
  98. package/src/lib/tool-router/types.ts +7 -0
  99. package/src/lib/workflow.test.ts +89 -21
  100. package/src/lib/workflow.ts +33 -18
  101. package/src/workflow.ts +6 -1
@@ -34,7 +34,11 @@ vi.mock("@temporalio/workflow", () => {
34
34
  condition: async (fn: () => boolean) => fn(),
35
35
  defineUpdate: (name: string) => ({ __type: "update", name }),
36
36
  defineQuery: (name: string) => ({ __type: "query", name }),
37
+ defineSignal: (name: string) => ({ __type: "signal", name }),
37
38
  setHandler: (_def: unknown, _handler: unknown) => {},
39
+ startChild: async () => ({ result: () => Promise.resolve(null) }),
40
+ workflowInfo: () => ({ taskQueue: "default-queue" }),
41
+ getExternalWorkflowHandle: () => ({ signal: async () => {} }),
38
42
  uuid4: () =>
39
43
  `00000000-0000-0000-0000-${String(++idCounter).padStart(12, "0")}`,
40
44
  ApplicationFailure: MockApplicationFailure,
@@ -151,7 +155,7 @@ describe("createSession edge cases", () => {
151
155
 
152
156
  const session = await createSession({
153
157
  agentName: "TestAgent",
154
- threadId: "thread-1",
158
+ thread: { mode: "new", threadId: "thread-1" },
155
159
  runAgent: createScriptedRunAgent([
156
160
  {
157
161
  message: "Need user input",
@@ -187,7 +191,7 @@ describe("createSession edge cases", () => {
187
191
 
188
192
  const session = await createSession({
189
193
  agentName: "TestAgent",
190
- threadId: "thread-1",
194
+ thread: { mode: "new", threadId: "thread-1" },
191
195
  runAgent: createScriptedRunAgent([
192
196
  {
193
197
  message: "bad calls",
@@ -233,7 +237,7 @@ describe("createSession edge cases", () => {
233
237
 
234
238
  const session = await createSession({
235
239
  agentName: "TestAgent",
236
- threadId: "thread-1",
240
+ thread: { mode: "new", threadId: "thread-1" },
237
241
  runAgent: createScriptedRunAgent([
238
242
  {
239
243
  message: "no id",
@@ -264,7 +268,7 @@ describe("createSession edge cases", () => {
264
268
 
265
269
  const session = await createSession({
266
270
  agentName: "TestAgent",
267
- threadId: "thread-1",
271
+ thread: { mode: "new", threadId: "thread-1" },
268
272
  runAgent: createScriptedRunAgent([
269
273
  {
270
274
  message: "I tried calling a tool",
@@ -302,7 +306,7 @@ describe("createSession edge cases", () => {
302
306
 
303
307
  const session = await createSession({
304
308
  agentName: "TestAgent",
305
- threadId: "thread-1",
309
+ thread: { mode: "new", threadId: "thread-1" },
306
310
  runAgent: createScriptedRunAgent([
307
311
  {
308
312
  message: "calling fail",
@@ -332,7 +336,7 @@ describe("createSession edge cases", () => {
332
336
 
333
337
  // --- Tool handler throws without recovery ---
334
338
 
335
- it("session fails when tool handler throws with no failure hook", async () => {
339
+ it("session completes when tool handler throws with no failure hook (error suppressed)", async () => {
336
340
  const { ops } = createMockThreadOps();
337
341
  let endReason: string | undefined;
338
342
 
@@ -347,7 +351,7 @@ describe("createSession edge cases", () => {
347
351
 
348
352
  const session = await createSession({
349
353
  agentName: "TestAgent",
350
- threadId: "thread-1",
354
+ thread: { mode: "new", threadId: "thread-1" },
351
355
  runAgent: createScriptedRunAgent([
352
356
  {
353
357
  message: "calling fail",
@@ -368,10 +372,10 @@ describe("createSession edge cases", () => {
368
372
  initialState: { systemPrompt: "test" },
369
373
  });
370
374
 
371
- await expect(session.runSession({ stateManager })).rejects.toThrow(
372
- "unrecoverable tool"
373
- );
374
- expect(endReason).toBe("failed");
375
+ const result = await session.runSession({ stateManager });
376
+ expect(result.exitReason).toBe("completed");
377
+ expect(result.finalMessage).toBe("done");
378
+ expect(endReason).toBe("completed");
375
379
  });
376
380
 
377
381
  // --- Metadata passed through to hooks ---
@@ -383,7 +387,7 @@ describe("createSession edge cases", () => {
383
387
 
384
388
  const session = await createSession({
385
389
  agentName: "TestAgent",
386
- threadId: "thread-1",
390
+ thread: { mode: "new", threadId: "thread-1" },
387
391
  metadata: { env: "test", version: 42 },
388
392
  runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
389
393
  threadOps: ops,
@@ -425,15 +429,16 @@ describe("createSession edge cases", () => {
425
429
  createdAt: new Date().toISOString(),
426
430
  }),
427
431
  forkSandbox: async () => "forked-sandbox-id",
432
+ pauseSandbox: async () => {},
428
433
  };
429
434
 
430
435
  const session = await createSession({
431
436
  agentName: "TestAgent",
432
- threadId: "thread-1",
437
+ thread: { mode: "new", threadId: "thread-1" },
433
438
  runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
434
439
  threadOps: ops,
435
440
  buildContextMessage: () => "go",
436
- sandbox: sandboxOps,
441
+ sandboxOps,
437
442
  });
438
443
 
439
444
  const stateManager = createAgentStateManager({
@@ -466,17 +471,18 @@ describe("createSession edge cases", () => {
466
471
  createdAt: new Date().toISOString(),
467
472
  }),
468
473
  forkSandbox: async () => "forked-sandbox-id",
474
+ pauseSandbox: async () => {},
469
475
  };
470
476
 
471
477
  const session = await createSession({
472
478
  agentName: "TestAgent",
473
- threadId: "thread-1",
479
+ thread: { mode: "new", threadId: "thread-1" },
474
480
  runAgent: async () => {
475
481
  throw new Error("LLM crash");
476
482
  },
477
483
  threadOps: ops,
478
484
  buildContextMessage: () => "go",
479
- sandbox: sandboxOps,
485
+ sandboxOps,
480
486
  });
481
487
 
482
488
  const stateManager = createAgentStateManager({
@@ -498,7 +504,7 @@ describe("createSession edge cases", () => {
498
504
 
499
505
  const session = await createSession({
500
506
  agentName: "TestAgent",
501
- threadId: "thread-1",
507
+ thread: { mode: "new", threadId: "thread-1" },
502
508
  runAgent: createScriptedRunAgent([]),
503
509
  threadOps: ops,
504
510
  buildContextMessage: () => "hi",
@@ -531,7 +537,7 @@ describe("createSession edge cases", () => {
531
537
 
532
538
  const session = await createSession({
533
539
  agentName: "TestAgent",
534
- threadId: "thread-1",
540
+ thread: { mode: "new", threadId: "thread-1" },
535
541
  runAgent: createScriptedRunAgent([
536
542
  {
537
543
  message: "t1",
@@ -562,15 +568,14 @@ describe("createSession edge cases", () => {
562
568
  expect(result.usage.totalOutputTokens).toBe(50);
563
569
  });
564
570
 
565
- // --- continueThread with no source thread ---
571
+ // --- Thread fork: new threadId from source ---
566
572
 
567
- it("continueThread generates new threadId and forks when source is provided", async () => {
573
+ it("fork thread mode generates new threadId and forks when source is provided", async () => {
568
574
  const { ops, log } = createMockThreadOps();
569
575
 
570
576
  const session = await createSession({
571
577
  agentName: "TestAgent",
572
- threadId: "original-thread",
573
- continueThread: true,
578
+ thread: { mode: "fork", threadId: "original-thread" },
574
579
  runAgent: createScriptedRunAgent([
575
580
  { message: "continued", toolCalls: [] },
576
581
  ]),
@@ -601,7 +606,7 @@ describe("createSession edge cases", () => {
601
606
 
602
607
  const session = await createSession({
603
608
  agentName: "TestAgent",
604
- threadId: "thread-1",
609
+ thread: { mode: "new", threadId: "thread-1" },
605
610
  maxTurns: 1,
606
611
  runAgent: createScriptedRunAgent([
607
612
  {
@@ -646,7 +651,7 @@ describe("createSession edge cases", () => {
646
651
 
647
652
  const session = await createSession({
648
653
  agentName: "TestAgent",
649
- threadId: "thread-1",
654
+ thread: { mode: "new", threadId: "thread-1" },
650
655
  processToolsInParallel: false,
651
656
  runAgent: createScriptedRunAgent([
652
657
  {
@@ -679,7 +684,7 @@ describe("createSession edge cases", () => {
679
684
 
680
685
  const session = await createSession({
681
686
  agentName: "TestAgent",
682
- threadId: "thread-1",
687
+ thread: { mode: "new", threadId: "thread-1" },
683
688
  runAgent: createScriptedRunAgent([
684
689
  {
685
690
  message: "mixed",
@@ -735,7 +740,7 @@ describe("createSession edge cases", () => {
735
740
 
736
741
  const session = await createSession({
737
742
  agentName: "TestAgent",
738
- threadId: "thread-1",
743
+ thread: { mode: "new", threadId: "thread-1" },
739
744
  runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
740
745
  threadOps: ops,
741
746
  buildContextMessage: () => [
@@ -769,7 +774,7 @@ describe("createSession edge cases", () => {
769
774
 
770
775
  const session = await createSession({
771
776
  agentName: "TestAgent",
772
- threadId: "thread-1",
777
+ thread: { mode: "new", threadId: "thread-1" },
773
778
  runAgent: createScriptedRunAgent([
774
779
  {
775
780
  message: "t1",
@@ -818,7 +823,7 @@ describe("createSession edge cases", () => {
818
823
 
819
824
  const session = await createSession({
820
825
  agentName: "TestAgent",
821
- threadId: "thread-1",
826
+ thread: { mode: "new", threadId: "thread-1" },
822
827
  runAgent: createScriptedRunAgent([
823
828
  {
824
829
  message: "self",
@@ -852,7 +857,7 @@ describe("createSession edge cases", () => {
852
857
 
853
858
  const session = await createSession({
854
859
  agentName: "TestAgent",
855
- threadId: "thread-1",
860
+ thread: { mode: "new", threadId: "thread-1" },
856
861
  runAgent: createScriptedRunAgent([
857
862
  {
858
863
  message: "calling",
@@ -908,15 +913,16 @@ describe("createSession edge cases", () => {
908
913
  destroySandbox: async () => {},
909
914
  snapshotSandbox: snapshotSpy,
910
915
  forkSandbox: async () => "forked-sandbox-id",
916
+ pauseSandbox: async () => {},
911
917
  };
912
918
 
913
919
  const session = await createSession({
914
920
  agentName: "TestAgent",
915
- threadId: "thread-1",
921
+ thread: { mode: "new", threadId: "thread-1" },
916
922
  runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
917
923
  threadOps: ops,
918
924
  buildContextMessage: () => "go",
919
- sandbox: sandboxOps,
925
+ sandboxOps,
920
926
  });
921
927
 
922
928
  const stateManager = createAgentStateManager({
@@ -935,7 +941,7 @@ describe("createSession edge cases", () => {
935
941
 
936
942
  const session = await createSession({
937
943
  agentName: "TestAgent",
938
- threadId: "thread-1",
944
+ thread: { mode: "new", threadId: "thread-1" },
939
945
  runAgent: createScriptedRunAgent([
940
946
  {
941
947
  message: "t1",
@@ -970,7 +976,7 @@ describe("createSession edge cases", () => {
970
976
 
971
977
  const session = await createSession({
972
978
  agentName: "TestAgent",
973
- threadId: "thread-1",
979
+ thread: { mode: "new", threadId: "thread-1" },
974
980
  maxTurns: 0,
975
981
  runAgent: createScriptedRunAgent([]),
976
982
  threadOps: ops,
@@ -988,4 +994,412 @@ describe("createSession edge cases", () => {
988
994
  expect(result.usage.turns).toBe(0);
989
995
  expect(result.finalMessage).toBeNull();
990
996
  });
997
+
998
+ // --- sandboxId returned from runSession ---
999
+
1000
+ it("returns sandboxId from runSession when sandbox is created", async () => {
1001
+ const { ops } = createMockThreadOps();
1002
+
1003
+ const sandboxOps: SandboxOps = {
1004
+ createSandbox: async () => ({ sandboxId: "sb-created" }),
1005
+ destroySandbox: async () => {},
1006
+ pauseSandbox: async () => {},
1007
+ snapshotSandbox: async () => ({
1008
+ sandboxId: "sb-1",
1009
+ providerId: "test",
1010
+ data: null,
1011
+ createdAt: new Date().toISOString(),
1012
+ }),
1013
+ forkSandbox: async () => "forked-sb",
1014
+ };
1015
+
1016
+ const session = await createSession({
1017
+ agentName: "TestAgent",
1018
+ thread: { mode: "new", threadId: "thread-1" },
1019
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1020
+ threadOps: ops,
1021
+ buildContextMessage: () => "go",
1022
+ sandboxOps,
1023
+ });
1024
+
1025
+ const stateManager = createAgentStateManager({
1026
+ initialState: { systemPrompt: "test" },
1027
+ });
1028
+
1029
+ const result = await session.runSession({ stateManager });
1030
+ expect((result as { sandboxId?: string }).sandboxId).toBe("sb-created");
1031
+ });
1032
+
1033
+ it("returns inherited sandboxId from runSession", async () => {
1034
+ const { ops } = createMockThreadOps();
1035
+
1036
+ const sandboxOps: SandboxOps = {
1037
+ createSandbox: async () => ({ sandboxId: "sb" }),
1038
+ destroySandbox: async () => {},
1039
+ pauseSandbox: async () => {},
1040
+ snapshotSandbox: async () => ({
1041
+ sandboxId: "sb",
1042
+ providerId: "test",
1043
+ data: null,
1044
+ createdAt: new Date().toISOString(),
1045
+ }),
1046
+ forkSandbox: async () => "forked-sb",
1047
+ };
1048
+
1049
+ const session = await createSession({
1050
+ agentName: "TestAgent",
1051
+ thread: { mode: "new", threadId: "thread-1" },
1052
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1053
+ threadOps: ops,
1054
+ buildContextMessage: () => "go",
1055
+ sandbox: { mode: "inherit", sandboxId: "inherited-sb" },
1056
+ sandboxOps,
1057
+ });
1058
+
1059
+ const stateManager = createAgentStateManager({
1060
+ initialState: { systemPrompt: "test" },
1061
+ });
1062
+
1063
+ const result = await session.runSession({ stateManager });
1064
+ expect((result as { sandboxId?: string }).sandboxId).toBe("inherited-sb");
1065
+ });
1066
+
1067
+ // --- Inherited sandbox is not destroyed ---
1068
+
1069
+ it("does not destroy inherited sandbox even when sandboxOps is provided", async () => {
1070
+ const { ops } = createMockThreadOps();
1071
+ const sandboxLog: string[] = [];
1072
+
1073
+ const sandboxOps: SandboxOps = {
1074
+ createSandbox: async () => {
1075
+ sandboxLog.push("create");
1076
+ return { sandboxId: "new-sb" };
1077
+ },
1078
+ destroySandbox: async (id: string) => {
1079
+ sandboxLog.push(`destroy:${id}`);
1080
+ },
1081
+ pauseSandbox: async (id: string) => {
1082
+ sandboxLog.push(`pause:${id}`);
1083
+ },
1084
+ snapshotSandbox: async () => ({
1085
+ sandboxId: "sb-1",
1086
+ providerId: "test",
1087
+ data: null,
1088
+ createdAt: new Date().toISOString(),
1089
+ }),
1090
+ forkSandbox: async () => "forked-sb",
1091
+ };
1092
+
1093
+ const session = await createSession({
1094
+ agentName: "TestAgent",
1095
+ thread: { mode: "new", threadId: "thread-1" },
1096
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1097
+ threadOps: ops,
1098
+ buildContextMessage: () => "go",
1099
+ sandboxOps,
1100
+ sandbox: { mode: "inherit", sandboxId: "inherited-sb" },
1101
+ });
1102
+
1103
+ const stateManager = createAgentStateManager({
1104
+ initialState: { systemPrompt: "test" },
1105
+ });
1106
+
1107
+ await session.runSession({ stateManager });
1108
+
1109
+ expect(sandboxLog).toHaveLength(0);
1110
+ });
1111
+
1112
+ // --- Sandbox fork ---
1113
+
1114
+ it("forks sandbox when sandbox init mode is fork", async () => {
1115
+ const { ops } = createMockThreadOps();
1116
+ const sandboxLog: string[] = [];
1117
+
1118
+ const sandboxOps: SandboxOps = {
1119
+ createSandbox: async () => {
1120
+ sandboxLog.push("create");
1121
+ return { sandboxId: "new-sb" };
1122
+ },
1123
+ destroySandbox: async (id: string) => {
1124
+ sandboxLog.push(`destroy:${id}`);
1125
+ },
1126
+ pauseSandbox: async () => {},
1127
+ snapshotSandbox: async () => ({
1128
+ sandboxId: "sb-1",
1129
+ providerId: "test",
1130
+ data: null,
1131
+ createdAt: new Date().toISOString(),
1132
+ }),
1133
+ forkSandbox: async (id: string) => {
1134
+ sandboxLog.push(`fork:${id}`);
1135
+ return `forked-from-${id}`;
1136
+ },
1137
+ };
1138
+
1139
+ const session = await createSession({
1140
+ agentName: "TestAgent",
1141
+ thread: { mode: "new", threadId: "thread-1" },
1142
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1143
+ threadOps: ops,
1144
+ buildContextMessage: () => "go",
1145
+ sandboxOps,
1146
+ sandbox: { mode: "fork", sandboxId: "paused-sb-1" },
1147
+ });
1148
+
1149
+ const stateManager = createAgentStateManager({
1150
+ initialState: { systemPrompt: "test" },
1151
+ });
1152
+
1153
+ const result = await session.runSession({ stateManager });
1154
+
1155
+ expect(sandboxLog).toContain("fork:paused-sb-1");
1156
+ expect(sandboxLog).not.toContain("create");
1157
+ expect((result as { sandboxId?: string }).sandboxId).toBe("forked-from-paused-sb-1");
1158
+ expect(sandboxLog).toContain("destroy:forked-from-paused-sb-1");
1159
+ });
1160
+
1161
+ // --- Forked sandbox is destroyed on exit ---
1162
+
1163
+ it("destroys forked sandbox on exit (not inherited)", async () => {
1164
+ const { ops } = createMockThreadOps();
1165
+ const sandboxLog: string[] = [];
1166
+
1167
+ const sandboxOps: SandboxOps = {
1168
+ createSandbox: async () => ({ sandboxId: "new-sb" }),
1169
+ destroySandbox: async (id: string) => {
1170
+ sandboxLog.push(`destroy:${id}`);
1171
+ },
1172
+ pauseSandbox: async () => {},
1173
+ snapshotSandbox: async () => ({
1174
+ sandboxId: "sb-1",
1175
+ providerId: "test",
1176
+ data: null,
1177
+ createdAt: new Date().toISOString(),
1178
+ }),
1179
+ forkSandbox: async () => "forked-sb",
1180
+ };
1181
+
1182
+ const session = await createSession({
1183
+ agentName: "TestAgent",
1184
+ thread: { mode: "new", threadId: "thread-1" },
1185
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1186
+ threadOps: ops,
1187
+ buildContextMessage: () => "go",
1188
+ sandboxOps,
1189
+ sandbox: { mode: "fork", sandboxId: "old-sb" },
1190
+ });
1191
+
1192
+ const stateManager = createAgentStateManager({
1193
+ initialState: { systemPrompt: "test" },
1194
+ });
1195
+
1196
+ await session.runSession({ stateManager });
1197
+
1198
+ expect(sandboxLog).toContain("destroy:forked-sb");
1199
+ });
1200
+
1201
+ // --- sandboxShutdown: "pause" ---
1202
+
1203
+ it("pauses sandbox on exit when sandboxShutdown is pause", async () => {
1204
+ const { ops } = createMockThreadOps();
1205
+ const sandboxLog: string[] = [];
1206
+
1207
+ const sandboxOps: SandboxOps = {
1208
+ createSandbox: async () => ({ sandboxId: "sb-pause-test" }),
1209
+ destroySandbox: async (id: string) => {
1210
+ sandboxLog.push(`destroy:${id}`);
1211
+ },
1212
+ pauseSandbox: async (id: string) => {
1213
+ sandboxLog.push(`pause:${id}`);
1214
+ },
1215
+ snapshotSandbox: async () => ({
1216
+ sandboxId: "sb-1",
1217
+ providerId: "test",
1218
+ data: null,
1219
+ createdAt: new Date().toISOString(),
1220
+ }),
1221
+ forkSandbox: async () => "forked-sb",
1222
+ };
1223
+
1224
+ const session = await createSession({
1225
+ agentName: "TestAgent",
1226
+ thread: { mode: "new", threadId: "thread-1" },
1227
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1228
+ threadOps: ops,
1229
+ buildContextMessage: () => "go",
1230
+ sandboxOps,
1231
+ sandboxShutdown: "pause",
1232
+ });
1233
+
1234
+ const stateManager = createAgentStateManager({
1235
+ initialState: { systemPrompt: "test" },
1236
+ });
1237
+
1238
+ await session.runSession({ stateManager });
1239
+
1240
+ expect(sandboxLog).toContain("pause:sb-pause-test");
1241
+ expect(sandboxLog).not.toContain("destroy:sb-pause-test");
1242
+ });
1243
+
1244
+ // --- sandboxShutdown: "pause-until-parent-close" ---
1245
+
1246
+ it("pauses sandbox on exit when sandboxShutdown is pause-until-parent-close", async () => {
1247
+ const { ops } = createMockThreadOps();
1248
+ const sandboxLog: string[] = [];
1249
+
1250
+ const sandboxOps: SandboxOps = {
1251
+ createSandbox: async () => ({ sandboxId: "sb-parent-close" }),
1252
+ destroySandbox: async (id: string) => {
1253
+ sandboxLog.push(`destroy:${id}`);
1254
+ },
1255
+ pauseSandbox: async (id: string) => {
1256
+ sandboxLog.push(`pause:${id}`);
1257
+ },
1258
+ snapshotSandbox: async () => ({
1259
+ sandboxId: "sb-1",
1260
+ providerId: "test",
1261
+ data: null,
1262
+ createdAt: new Date().toISOString(),
1263
+ }),
1264
+ forkSandbox: async () => "forked-sb",
1265
+ };
1266
+
1267
+ const session = await createSession({
1268
+ agentName: "TestAgent",
1269
+ thread: { mode: "new", threadId: "thread-1" },
1270
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1271
+ threadOps: ops,
1272
+ buildContextMessage: () => "go",
1273
+ sandboxOps,
1274
+ sandboxShutdown: "pause-until-parent-close",
1275
+ });
1276
+
1277
+ const stateManager = createAgentStateManager({
1278
+ initialState: { systemPrompt: "test" },
1279
+ });
1280
+
1281
+ await session.runSession({ stateManager });
1282
+
1283
+ expect(sandboxLog).toContain("pause:sb-parent-close");
1284
+ expect(sandboxLog).not.toContain("destroy:sb-parent-close");
1285
+ });
1286
+
1287
+ // --- Throws when sandbox fork/continue provided without sandboxOps ---
1288
+
1289
+ it("throws when sandbox fork mode is set without sandboxOps", async () => {
1290
+ const { ops } = createMockThreadOps();
1291
+
1292
+ const session = await createSession({
1293
+ agentName: "TestAgent",
1294
+ thread: { mode: "new", threadId: "thread-1" },
1295
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1296
+ threadOps: ops,
1297
+ buildContextMessage: () => "go",
1298
+ sandbox: { mode: "fork", sandboxId: "prev-sb" },
1299
+ });
1300
+
1301
+ const stateManager = createAgentStateManager({
1302
+ initialState: { systemPrompt: "test" },
1303
+ });
1304
+
1305
+ await expect(session.runSession({ stateManager })).rejects.toThrow(
1306
+ "No sandboxOps provided — cannot fork sandbox"
1307
+ );
1308
+ });
1309
+
1310
+ // --- No sandboxId returned when no sandbox ---
1311
+
1312
+ it("does not return sandboxId when no sandbox is configured", async () => {
1313
+ const { ops } = createMockThreadOps();
1314
+
1315
+ const session = await createSession({
1316
+ agentName: "TestAgent",
1317
+ thread: { mode: "new", threadId: "thread-1" },
1318
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1319
+ threadOps: ops,
1320
+ buildContextMessage: () => "go",
1321
+ });
1322
+
1323
+ const stateManager = createAgentStateManager({
1324
+ initialState: { systemPrompt: "test" },
1325
+ });
1326
+
1327
+ const result = await session.runSession({ stateManager });
1328
+ expect((result as { sandboxId?: string }).sandboxId).toBeUndefined();
1329
+ });
1330
+
1331
+ // --- Thread: defaults to new thread when no thread field provided ---
1332
+
1333
+ it("defaults to new thread without fork when no thread field is provided", async () => {
1334
+ const { ops, log } = createMockThreadOps();
1335
+
1336
+ const session = await createSession({
1337
+ agentName: "TestAgent",
1338
+ runAgent: createScriptedRunAgent([
1339
+ { message: "done", toolCalls: [] },
1340
+ ]),
1341
+ threadOps: ops,
1342
+ buildContextMessage: () => "go",
1343
+ });
1344
+
1345
+ const stateManager = createAgentStateManager({
1346
+ initialState: { systemPrompt: "test" },
1347
+ });
1348
+
1349
+ const result = await session.runSession({ stateManager });
1350
+ expect(result.exitReason).toBe("completed");
1351
+
1352
+ const forkOps = log.filter((l) => l.op === "forkThread");
1353
+ expect(forkOps).toHaveLength(0);
1354
+
1355
+ const systemOps = log.filter((l) => l.op === "appendSystemMessage");
1356
+ expect(systemOps).toHaveLength(1);
1357
+ });
1358
+
1359
+ // --- Sandbox pause on error ---
1360
+
1361
+ it("pauses sandbox even when session fails if sandboxShutdown is pause", async () => {
1362
+ const { ops } = createMockThreadOps();
1363
+ const sandboxLog: string[] = [];
1364
+
1365
+ const sandboxOps: SandboxOps = {
1366
+ createSandbox: async () => ({ sandboxId: "sb-err" }),
1367
+ destroySandbox: async (id: string) => {
1368
+ sandboxLog.push(`destroy:${id}`);
1369
+ },
1370
+ pauseSandbox: async (id: string) => {
1371
+ sandboxLog.push(`pause:${id}`);
1372
+ },
1373
+ snapshotSandbox: async () => ({
1374
+ sandboxId: "sb-1",
1375
+ providerId: "test",
1376
+ data: null,
1377
+ createdAt: new Date().toISOString(),
1378
+ }),
1379
+ forkSandbox: async () => "forked-sb",
1380
+ };
1381
+
1382
+ const session = await createSession({
1383
+ agentName: "TestAgent",
1384
+ thread: { mode: "new", threadId: "thread-1" },
1385
+ runAgent: async () => {
1386
+ throw new Error("crash");
1387
+ },
1388
+ threadOps: ops,
1389
+ buildContextMessage: () => "go",
1390
+ sandboxOps,
1391
+ sandboxShutdown: "pause",
1392
+ });
1393
+
1394
+ const stateManager = createAgentStateManager({
1395
+ initialState: { systemPrompt: "test" },
1396
+ });
1397
+
1398
+ await expect(session.runSession({ stateManager })).rejects.toThrow(
1399
+ "crash"
1400
+ );
1401
+
1402
+ expect(sandboxLog).toContain("pause:sb-err");
1403
+ expect(sandboxLog).not.toContain("destroy:sb-err");
1404
+ });
991
1405
  });