zeitlich 0.2.32 → 0.2.34

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 (143) hide show
  1. package/README.md +28 -16
  2. package/dist/{activities-FIXVz7DT.d.ts → activities-JOqPfKP0.d.cts} +6 -5
  3. package/dist/{activities-DA-bQM12.d.cts → activities-WwMsjRwm.d.ts} +6 -5
  4. package/dist/adapters/sandbox/bedrock/index.cjs +2 -0
  5. package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
  6. package/dist/adapters/sandbox/bedrock/index.d.cts +4 -3
  7. package/dist/adapters/sandbox/bedrock/index.d.ts +4 -3
  8. package/dist/adapters/sandbox/bedrock/index.js +2 -0
  9. package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
  10. package/dist/adapters/sandbox/bedrock/workflow.cjs +1 -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 +1 -0
  15. package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
  16. package/dist/adapters/sandbox/daytona/index.cjs +2 -0
  17. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  18. package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
  19. package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
  20. package/dist/adapters/sandbox/daytona/index.js +2 -0
  21. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  22. package/dist/adapters/sandbox/daytona/workflow.cjs +1 -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 +1 -0
  27. package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
  28. package/dist/adapters/sandbox/e2b/index.cjs +3 -0
  29. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  30. package/dist/adapters/sandbox/e2b/index.d.cts +2 -1
  31. package/dist/adapters/sandbox/e2b/index.d.ts +2 -1
  32. package/dist/adapters/sandbox/e2b/index.js +3 -0
  33. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  34. package/dist/adapters/sandbox/e2b/workflow.cjs +1 -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 +1 -0
  39. package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
  40. package/dist/adapters/sandbox/inmemory/index.cjs +2 -0
  41. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  42. package/dist/adapters/sandbox/inmemory/index.d.cts +2 -1
  43. package/dist/adapters/sandbox/inmemory/index.d.ts +2 -1
  44. package/dist/adapters/sandbox/inmemory/index.js +2 -0
  45. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  46. package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -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 +1 -0
  51. package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
  52. package/dist/adapters/thread/anthropic/index.cjs +18 -2
  53. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  54. package/dist/adapters/thread/anthropic/index.d.cts +12 -11
  55. package/dist/adapters/thread/anthropic/index.d.ts +12 -11
  56. package/dist/adapters/thread/anthropic/index.js +18 -2
  57. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  58. package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
  59. package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
  60. package/dist/adapters/thread/google-genai/index.cjs +29 -8
  61. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  62. package/dist/adapters/thread/google-genai/index.d.cts +8 -8
  63. package/dist/adapters/thread/google-genai/index.d.ts +8 -8
  64. package/dist/adapters/thread/google-genai/index.js +29 -8
  65. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  66. package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
  67. package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
  68. package/dist/adapters/thread/langchain/index.cjs +42 -23
  69. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  70. package/dist/adapters/thread/langchain/index.d.cts +13 -11
  71. package/dist/adapters/thread/langchain/index.d.ts +13 -11
  72. package/dist/adapters/thread/langchain/index.js +42 -23
  73. package/dist/adapters/thread/langchain/index.js.map +1 -1
  74. package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
  75. package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
  76. package/dist/index.cjs +148 -34
  77. package/dist/index.cjs.map +1 -1
  78. package/dist/index.d.cts +32 -16
  79. package/dist/index.d.ts +32 -16
  80. package/dist/index.js +147 -35
  81. package/dist/index.js.map +1 -1
  82. package/dist/{proxy-CTCYWjkr.d.cts → proxy-BesT2ioL.d.cts} +1 -1
  83. package/dist/{proxy-Br4unLTC.d.ts → proxy-Bz6wXYW-.d.ts} +1 -1
  84. package/dist/{thread-manager-Cv_BR28i.d.cts → thread-manager-CCVAOK8g.d.cts} +1 -1
  85. package/dist/{thread-manager-CUubPYPH.d.cts → thread-manager-Cf_34H8w.d.cts} +1 -1
  86. package/dist/{thread-manager-YJLoc1vH.d.ts → thread-manager-ClKAQx78.d.ts} +1 -1
  87. package/dist/{thread-manager-DKWxHUzD.d.ts → thread-manager-DarJIK_b.d.ts} +1 -1
  88. package/dist/{types-Bpq5fDI5.d.cts → types-BGLW5Zyj.d.ts} +35 -20
  89. package/dist/{types-BxiT8w9d.d.ts → types-BVUmLYpj.d.ts} +1 -1
  90. package/dist/{types-DUvEZSDe.d.cts → types-CBH54cwr.d.cts} +1 -1
  91. package/dist/{types-NJDyMyUx.d.cts → types-DPAZ3KCs.d.cts} +1 -1
  92. package/dist/{types-CheCTLeV.d.ts → types-DlLajQcu.d.cts} +35 -20
  93. package/dist/{types-AujBIMMn.d.cts → types-DxCpFNv_.d.cts} +4 -0
  94. package/dist/{types-AujBIMMn.d.ts → types-DxCpFNv_.d.ts} +4 -0
  95. package/dist/{types-DBk-C8zM.d.ts → types-wiGLvxWf.d.ts} +1 -1
  96. package/dist/{workflow-BWKQcz9d.d.cts → workflow-_ZGcacCK.d.ts} +32 -4
  97. package/dist/{workflow-D8wK7TJY.d.ts → workflow-hocXpLwg.d.cts} +32 -4
  98. package/dist/workflow.cjs +126 -30
  99. package/dist/workflow.cjs.map +1 -1
  100. package/dist/workflow.d.cts +3 -3
  101. package/dist/workflow.d.ts +3 -3
  102. package/dist/workflow.js +126 -31
  103. package/dist/workflow.js.map +1 -1
  104. package/package.json +1 -1
  105. package/src/adapters/sandbox/bedrock/index.ts +4 -0
  106. package/src/adapters/sandbox/bedrock/proxy.ts +1 -0
  107. package/src/adapters/sandbox/daytona/index.ts +4 -0
  108. package/src/adapters/sandbox/daytona/proxy.ts +1 -0
  109. package/src/adapters/sandbox/e2b/index.ts +4 -0
  110. package/src/adapters/sandbox/e2b/proxy.ts +1 -0
  111. package/src/adapters/sandbox/inmemory/index.ts +4 -0
  112. package/src/adapters/sandbox/inmemory/proxy.ts +1 -0
  113. package/src/adapters/thread/anthropic/activities.ts +4 -3
  114. package/src/adapters/thread/anthropic/model-invoker.ts +15 -5
  115. package/src/adapters/thread/google-genai/activities.ts +4 -3
  116. package/src/adapters/thread/google-genai/model-invoker.ts +24 -11
  117. package/src/adapters/thread/langchain/activities.ts +3 -3
  118. package/src/adapters/thread/langchain/model-invoker.ts +63 -34
  119. package/src/index.ts +1 -0
  120. package/src/lib/activity.ts +36 -9
  121. package/src/lib/lifecycle.ts +7 -3
  122. package/src/lib/model/helpers.ts +1 -0
  123. package/src/lib/model/index.ts +1 -0
  124. package/src/lib/model/proxy.ts +50 -0
  125. package/src/lib/sandbox/manager.ts +7 -0
  126. package/src/lib/sandbox/types.ts +4 -0
  127. package/src/lib/session/session-edge-cases.integration.test.ts +194 -0
  128. package/src/lib/session/session.integration.test.ts +5 -0
  129. package/src/lib/session/session.ts +9 -0
  130. package/src/lib/session/types.ts +5 -0
  131. package/src/lib/subagent/define.ts +1 -1
  132. package/src/lib/subagent/handler.ts +142 -32
  133. package/src/lib/subagent/index.ts +5 -1
  134. package/src/lib/subagent/signals.ts +8 -1
  135. package/src/lib/subagent/subagent.integration.test.ts +532 -25
  136. package/src/lib/subagent/types.ts +32 -15
  137. package/src/lib/subagent/workflow.ts +26 -13
  138. package/src/lib/virtual-fs/manager.ts +1 -1
  139. package/src/lib/virtual-fs/types.ts +2 -2
  140. package/src/lib/virtual-fs/virtual-fs.test.ts +2 -2
  141. package/src/workflow.ts +3 -0
  142. package/src/lib/.env +0 -1
  143. package/src/tools/bash/.env +0 -1
@@ -131,6 +131,8 @@ export interface SandboxProvider<
131
131
  get(sandboxId: string): Promise<TSandbox>;
132
132
  destroy(sandboxId: string): Promise<void>;
133
133
  pause(sandboxId: string, ttlSeconds?: number): Promise<void>;
134
+ /** Resume a paused sandbox. No-op if already running. */
135
+ resume(sandboxId: string): Promise<void>;
134
136
  snapshot(sandboxId: string): Promise<SandboxSnapshot>;
135
137
  restore(snapshot: SandboxSnapshot): Promise<Sandbox>;
136
138
  fork(sandboxId: string): Promise<Sandbox>;
@@ -150,6 +152,8 @@ export interface SandboxOps<
150
152
  ): Promise<{ sandboxId: string } | null>;
151
153
  destroySandbox(sandboxId: string): Promise<void>;
152
154
  pauseSandbox(sandboxId: string): Promise<void>;
155
+ /** Resume a paused sandbox. No-op if already running. */
156
+ resumeSandbox(sandboxId: string): Promise<void>;
153
157
  snapshotSandbox(sandboxId: string): Promise<SandboxSnapshot>;
154
158
  forkSandbox(sandboxId: string): Promise<string>;
155
159
  }
@@ -434,6 +434,7 @@ describe("createSession edge cases", () => {
434
434
  }),
435
435
  forkSandbox: async () => "forked-sandbox-id",
436
436
  pauseSandbox: async () => {},
437
+ resumeSandbox: async () => {},
437
438
  };
438
439
 
439
440
  const session = await createSession({
@@ -476,6 +477,7 @@ describe("createSession edge cases", () => {
476
477
  }),
477
478
  forkSandbox: async () => "forked-sandbox-id",
478
479
  pauseSandbox: async () => {},
480
+ resumeSandbox: async () => {},
479
481
  };
480
482
 
481
483
  const session = await createSession({
@@ -939,6 +941,7 @@ describe("createSession edge cases", () => {
939
941
  snapshotSandbox: snapshotSpy,
940
942
  forkSandbox: async () => "forked-sandbox-id",
941
943
  pauseSandbox: async () => {},
944
+ resumeSandbox: async () => {},
942
945
  };
943
946
 
944
947
  const session = await createSession({
@@ -1029,6 +1032,7 @@ describe("createSession edge cases", () => {
1029
1032
  createSandbox: async () => ({ sandboxId: "sb-created" }),
1030
1033
  destroySandbox: async () => {},
1031
1034
  pauseSandbox: async () => {},
1035
+ resumeSandbox: async () => {},
1032
1036
  snapshotSandbox: async () => ({
1033
1037
  sandboxId: "sb-1",
1034
1038
  providerId: "test",
@@ -1062,6 +1066,7 @@ describe("createSession edge cases", () => {
1062
1066
  createSandbox: async () => ({ sandboxId: "sb" }),
1063
1067
  destroySandbox: async () => {},
1064
1068
  pauseSandbox: async () => {},
1069
+ resumeSandbox: async () => {},
1065
1070
  snapshotSandbox: async () => ({
1066
1071
  sandboxId: "sb",
1067
1072
  providerId: "test",
@@ -1106,6 +1111,7 @@ describe("createSession edge cases", () => {
1106
1111
  pauseSandbox: async (id: string) => {
1107
1112
  sandboxLog.push(`pause:${id}`);
1108
1113
  },
1114
+ resumeSandbox: async () => {},
1109
1115
  snapshotSandbox: async () => ({
1110
1116
  sandboxId: "sb-1",
1111
1117
  providerId: "test",
@@ -1149,6 +1155,7 @@ describe("createSession edge cases", () => {
1149
1155
  sandboxLog.push(`destroy:${id}`);
1150
1156
  },
1151
1157
  pauseSandbox: async () => {},
1158
+ resumeSandbox: async () => {},
1152
1159
  snapshotSandbox: async () => ({
1153
1160
  sandboxId: "sb-1",
1154
1161
  providerId: "test",
@@ -1195,6 +1202,7 @@ describe("createSession edge cases", () => {
1195
1202
  sandboxLog.push(`destroy:${id}`);
1196
1203
  },
1197
1204
  pauseSandbox: async () => {},
1205
+ resumeSandbox: async () => {},
1198
1206
  snapshotSandbox: async () => ({
1199
1207
  sandboxId: "sb-1",
1200
1208
  providerId: "test",
@@ -1237,6 +1245,7 @@ describe("createSession edge cases", () => {
1237
1245
  pauseSandbox: async (id: string) => {
1238
1246
  sandboxLog.push(`pause:${id}`);
1239
1247
  },
1248
+ resumeSandbox: async () => {},
1240
1249
  snapshotSandbox: async () => ({
1241
1250
  sandboxId: "sb-1",
1242
1251
  providerId: "test",
@@ -1280,6 +1289,7 @@ describe("createSession edge cases", () => {
1280
1289
  pauseSandbox: async (id: string) => {
1281
1290
  sandboxLog.push(`pause:${id}`);
1282
1291
  },
1292
+ resumeSandbox: async () => {},
1283
1293
  snapshotSandbox: async () => ({
1284
1294
  sandboxId: "sb-1",
1285
1295
  providerId: "test",
@@ -1395,6 +1405,7 @@ describe("createSession edge cases", () => {
1395
1405
  pauseSandbox: async (id: string) => {
1396
1406
  sandboxLog.push(`pause:${id}`);
1397
1407
  },
1408
+ resumeSandbox: async () => {},
1398
1409
  snapshotSandbox: async () => ({
1399
1410
  sandboxId: "sb-1",
1400
1411
  providerId: "test",
@@ -1427,4 +1438,187 @@ describe("createSession edge cases", () => {
1427
1438
  expect(sandboxLog).toContain("pause:sb-err");
1428
1439
  expect(sandboxLog).not.toContain("destroy:sb-err");
1429
1440
  });
1441
+
1442
+ // --- sandboxShutdown: "keep-until-parent-close" ---
1443
+
1444
+ it("keeps sandbox running on exit when sandboxShutdown is keep-until-parent-close", async () => {
1445
+ const { ops } = createMockThreadOps();
1446
+ const sandboxLog: string[] = [];
1447
+
1448
+ const sandboxOps: SandboxOps = {
1449
+ createSandbox: async () => ({ sandboxId: "sb-keep-parent" }),
1450
+ destroySandbox: async (id: string) => {
1451
+ sandboxLog.push(`destroy:${id}`);
1452
+ },
1453
+ pauseSandbox: async (id: string) => {
1454
+ sandboxLog.push(`pause:${id}`);
1455
+ },
1456
+ resumeSandbox: async () => {},
1457
+ snapshotSandbox: async () => ({
1458
+ sandboxId: "sb-1",
1459
+ providerId: "test",
1460
+ data: null,
1461
+ createdAt: new Date().toISOString(),
1462
+ }),
1463
+ forkSandbox: async () => "forked-sb",
1464
+ };
1465
+
1466
+ const session = await createSession({
1467
+ agentName: "TestAgent",
1468
+ thread: { mode: "new", threadId: "thread-1" },
1469
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1470
+ threadOps: ops,
1471
+ buildContextMessage: () => "go",
1472
+ sandboxOps,
1473
+ sandboxShutdown: "keep-until-parent-close",
1474
+ });
1475
+
1476
+ const stateManager = createAgentStateManager({
1477
+ initialState: { systemPrompt: "test" },
1478
+ });
1479
+
1480
+ await session.runSession({ stateManager });
1481
+
1482
+ expect(sandboxLog).not.toContain("pause:sb-keep-parent");
1483
+ expect(sandboxLog).not.toContain("destroy:sb-keep-parent");
1484
+ });
1485
+
1486
+ it("keeps sandbox running on error when sandboxShutdown is keep-until-parent-close", async () => {
1487
+ const { ops } = createMockThreadOps();
1488
+ const sandboxLog: string[] = [];
1489
+
1490
+ const sandboxOps: SandboxOps = {
1491
+ createSandbox: async () => ({ sandboxId: "sb-keep-err" }),
1492
+ destroySandbox: async (id: string) => {
1493
+ sandboxLog.push(`destroy:${id}`);
1494
+ },
1495
+ pauseSandbox: async (id: string) => {
1496
+ sandboxLog.push(`pause:${id}`);
1497
+ },
1498
+ resumeSandbox: async () => {},
1499
+ snapshotSandbox: async () => ({
1500
+ sandboxId: "sb-1",
1501
+ providerId: "test",
1502
+ data: null,
1503
+ createdAt: new Date().toISOString(),
1504
+ }),
1505
+ forkSandbox: async () => "forked-sb",
1506
+ };
1507
+
1508
+ const session = await createSession({
1509
+ agentName: "TestAgent",
1510
+ thread: { mode: "new", threadId: "thread-1" },
1511
+ runAgent: async () => {
1512
+ throw new Error("crash");
1513
+ },
1514
+ threadOps: ops,
1515
+ buildContextMessage: () => "go",
1516
+ sandboxOps,
1517
+ sandboxShutdown: "keep-until-parent-close",
1518
+ });
1519
+
1520
+ const stateManager = createAgentStateManager({
1521
+ initialState: { systemPrompt: "test" },
1522
+ });
1523
+
1524
+ await expect(session.runSession({ stateManager })).rejects.toThrow(
1525
+ "crash"
1526
+ );
1527
+
1528
+ expect(sandboxLog).not.toContain("pause:sb-keep-err");
1529
+ expect(sandboxLog).not.toContain("destroy:sb-keep-err");
1530
+ });
1531
+
1532
+ // --- sandbox continue calls resumeSandbox only for pause-until-parent-close ---
1533
+
1534
+ it("calls resumeSandbox when sandbox mode is continue with pause-until-parent-close", async () => {
1535
+ const { ops } = createMockThreadOps();
1536
+ const sandboxLog: string[] = [];
1537
+
1538
+ const sandboxOps: SandboxOps = {
1539
+ createSandbox: async () => ({ sandboxId: "new-sb" }),
1540
+ destroySandbox: async (id: string) => {
1541
+ sandboxLog.push(`destroy:${id}`);
1542
+ },
1543
+ pauseSandbox: async (id: string) => {
1544
+ sandboxLog.push(`pause:${id}`);
1545
+ },
1546
+ resumeSandbox: async (id: string) => {
1547
+ sandboxLog.push(`resume:${id}`);
1548
+ },
1549
+ snapshotSandbox: async () => ({
1550
+ sandboxId: "sb-1",
1551
+ providerId: "test",
1552
+ data: null,
1553
+ createdAt: new Date().toISOString(),
1554
+ }),
1555
+ forkSandbox: async () => "forked-sb",
1556
+ };
1557
+
1558
+ const session = await createSession({
1559
+ agentName: "TestAgent",
1560
+ thread: { mode: "new", threadId: "thread-1" },
1561
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1562
+ threadOps: ops,
1563
+ buildContextMessage: () => "go",
1564
+ sandboxOps,
1565
+ sandbox: { mode: "continue", sandboxId: "paused-sb" },
1566
+ sandboxShutdown: "pause-until-parent-close",
1567
+ });
1568
+
1569
+ const stateManager = createAgentStateManager({
1570
+ initialState: { systemPrompt: "test" },
1571
+ });
1572
+
1573
+ await session.runSession({ stateManager });
1574
+
1575
+ expect(sandboxLog).toContain("resume:paused-sb");
1576
+ expect(sandboxLog).toContain("pause:paused-sb");
1577
+ });
1578
+
1579
+ it("skips resumeSandbox when sandbox mode is continue with keep-until-parent-close", async () => {
1580
+ const { ops } = createMockThreadOps();
1581
+ const sandboxLog: string[] = [];
1582
+
1583
+ const sandboxOps: SandboxOps = {
1584
+ createSandbox: async () => ({ sandboxId: "new-sb" }),
1585
+ destroySandbox: async (id: string) => {
1586
+ sandboxLog.push(`destroy:${id}`);
1587
+ },
1588
+ pauseSandbox: async (id: string) => {
1589
+ sandboxLog.push(`pause:${id}`);
1590
+ },
1591
+ resumeSandbox: async (id: string) => {
1592
+ sandboxLog.push(`resume:${id}`);
1593
+ },
1594
+ snapshotSandbox: async () => ({
1595
+ sandboxId: "sb-1",
1596
+ providerId: "test",
1597
+ data: null,
1598
+ createdAt: new Date().toISOString(),
1599
+ }),
1600
+ forkSandbox: async () => "forked-sb",
1601
+ };
1602
+
1603
+ const session = await createSession({
1604
+ agentName: "TestAgent",
1605
+ thread: { mode: "new", threadId: "thread-1" },
1606
+ runAgent: createScriptedRunAgent([{ message: "done", toolCalls: [] }]),
1607
+ threadOps: ops,
1608
+ buildContextMessage: () => "go",
1609
+ sandboxOps,
1610
+ sandbox: { mode: "continue", sandboxId: "kept-sb" },
1611
+ sandboxShutdown: "keep-until-parent-close",
1612
+ });
1613
+
1614
+ const stateManager = createAgentStateManager({
1615
+ initialState: { systemPrompt: "test" },
1616
+ });
1617
+
1618
+ await session.runSession({ stateManager });
1619
+
1620
+ expect(sandboxLog).not.toContain("resume:kept-sb");
1621
+ expect(sandboxLog).not.toContain("pause:kept-sb");
1622
+ expect(sandboxLog).not.toContain("destroy:kept-sb");
1623
+ });
1430
1624
  });
@@ -518,6 +518,7 @@ describe("createSession integration", () => {
518
518
  }),
519
519
  forkSandbox: async () => "forked-sandbox-id",
520
520
  pauseSandbox: async () => {},
521
+ resumeSandbox: async () => {},
521
522
  };
522
523
 
523
524
  const session = await createSession({
@@ -559,6 +560,7 @@ describe("createSession integration", () => {
559
560
  }),
560
561
  forkSandbox: async () => "forked-sandbox-id",
561
562
  pauseSandbox: async () => {},
563
+ resumeSandbox: async () => {},
562
564
  };
563
565
 
564
566
  const session = await createSession({
@@ -600,6 +602,7 @@ describe("createSession integration", () => {
600
602
  createSandbox: async () => ({ sandboxId: "sb" }),
601
603
  destroySandbox: async () => {},
602
604
  pauseSandbox: async () => {},
605
+ resumeSandbox: async () => {},
603
606
  snapshotSandbox: async () => ({
604
607
  sandboxId: "sb",
605
608
  providerId: "test",
@@ -820,6 +823,7 @@ describe("createSession integration", () => {
820
823
  }),
821
824
  forkSandbox: async () => "forked-sandbox-id",
822
825
  pauseSandbox: async () => {},
826
+ resumeSandbox: async () => {},
823
827
  };
824
828
 
825
829
  const session = await createSession({
@@ -870,6 +874,7 @@ describe("createSession integration", () => {
870
874
  }),
871
875
  forkSandbox: async () => "forked-sandbox-id",
872
876
  pauseSandbox: async () => {},
877
+ resumeSandbox: async () => {},
873
878
  };
874
879
 
875
880
  const session = await createSession({
@@ -106,6 +106,7 @@ export async function createSession<
106
106
  thread: threadInit,
107
107
  sandbox: sandboxInit,
108
108
  sandboxShutdown = "destroy",
109
+ onSandboxReady,
109
110
  virtualFs: virtualFsConfig,
110
111
  virtualFsOps,
111
112
  }: SessionConfig<T, M, TContent>): Promise<ZeitlichSession<M, boolean>> {
@@ -236,6 +237,9 @@ export async function createSession<
236
237
  }
237
238
  sandboxId = (sandboxInit as { mode: "continue"; sandboxId: string })
238
239
  .sandboxId;
240
+ if (sandboxShutdown === "pause-until-parent-close") {
241
+ await sandboxOps.resumeSandbox(sandboxId);
242
+ }
239
243
  sandboxOwned = true;
240
244
  } else if (sandboxMode === "fork") {
241
245
  if (!sandboxOps) {
@@ -262,6 +266,10 @@ export async function createSession<
262
266
  }
263
267
  }
264
268
 
269
+ if (sandboxId && onSandboxReady) {
270
+ onSandboxReady(sandboxId);
271
+ }
272
+
265
273
  // --- Virtual filesystem init (independent of sandbox) ----------------
266
274
  if (virtualFsConfig) {
267
275
  if (!virtualFsOps) {
@@ -467,6 +475,7 @@ export async function createSession<
467
475
  await sandboxOps.pauseSandbox(sandboxId);
468
476
  break;
469
477
  case "keep":
478
+ case "keep-until-parent-close":
470
479
  break;
471
480
  }
472
481
  }
@@ -180,6 +180,11 @@ export interface SessionConfig<
180
180
  * Has no effect when the sandbox is inherited (`sandbox.mode === "inherit"`).
181
181
  */
182
182
  sandboxShutdown?: SubagentSandboxShutdown;
183
+ /**
184
+ * Called as soon as the sandbox is created (or resumed/forked), before the
185
+ * agent loop starts. Useful for signalling sandbox readiness to a parent.
186
+ */
187
+ onSandboxReady?: (sandboxId: string) => void;
183
188
 
184
189
  // ---------------------------------------------------------------------------
185
190
  // Virtual filesystem
@@ -21,7 +21,7 @@ import type { SubagentArgs } from "./tool";
21
21
  * // With parent-specific overrides
22
22
  * export const researcher = defineSubagent(researcherWorkflow, {
23
23
  * thread: "fork",
24
- * sandbox: { source: "own", shutdown: "pause" },
24
+ * sandbox: { source: "own", continuation: "fork", shutdown: "pause" },
25
25
  * hooks: {
26
26
  * onPostExecution: ({ result }) => console.log(result),
27
27
  * },
@@ -22,19 +22,40 @@ import type {
22
22
  SandboxInit,
23
23
  SubagentSandboxShutdown,
24
24
  } from "../lifecycle";
25
- import { childResultSignal, destroySandboxSignal } from "./signals";
25
+ import {
26
+ childResultSignal,
27
+ childSandboxReadySignal,
28
+ destroySandboxSignal,
29
+ } from "./signals";
26
30
 
27
- /**
28
- * Resolve the shorthand/object `SubagentSandboxConfig` into a normalized form.
29
- */
30
- function resolveSandboxConfig(config?: SubagentSandboxConfig): {
31
+ /** Normalized sandbox config after resolving the union. */
32
+ interface ResolvedSandboxConfig {
31
33
  source: "none" | "inherit" | "own";
34
+ init: "per-call" | "once";
35
+ continuation: "continue" | "fork";
32
36
  shutdown?: SubagentSandboxShutdown;
33
- } {
34
- if (!config || config === "none") return { source: "none" };
35
- if (config === "inherit") return { source: "inherit" };
36
- if (config === "own") return { source: "own" };
37
- return { source: "own", shutdown: config.shutdown };
37
+ }
38
+
39
+ function resolveSandboxConfig(
40
+ config?: SubagentSandboxConfig
41
+ ): ResolvedSandboxConfig {
42
+ if (!config || config === "none") {
43
+ return { source: "none", init: "per-call", continuation: "fork" };
44
+ }
45
+ if (config.source === "inherit") {
46
+ return {
47
+ source: "inherit",
48
+ init: "per-call",
49
+ continuation: config.continuation,
50
+ shutdown: config.shutdown,
51
+ };
52
+ }
53
+ return {
54
+ source: "own",
55
+ init: config.init ?? "per-call",
56
+ continuation: config.continuation,
57
+ shutdown: config.shutdown,
58
+ };
38
59
  }
39
60
 
40
61
  /**
@@ -65,13 +86,30 @@ export function createSubagentHandler<
65
86
  string,
66
87
  Awaited<ReturnType<typeof startChild>>
67
88
  >();
68
- /** Maps childThreadId → sandboxId for sandbox continuation across invocations */
89
+ /** Maps childThreadId → sandboxId for sandbox continuation across invocations (init: per-call) */
69
90
  const threadSandboxes = new Map<string, string>();
91
+ /** Maps agentName → sandboxId for persistent sandboxes (init: once) */
92
+ const persistentSandboxes = new Map<string, string>();
93
+ /** Tracks agents whose first lazy sandbox creation is in-flight (guards concurrent init) */
94
+ const persistentSandboxCreating = new Set<string>();
95
+ /** Reverse lookup: childWorkflowId → agentName for in-flight lazy creators */
96
+ const lazyCreatorAgent = new Map<string, string>();
70
97
 
71
98
  setHandler(childResultSignal, ({ childWorkflowId, result }) => {
72
99
  childResults.set(childWorkflowId, result);
73
100
  });
74
101
 
102
+ setHandler(
103
+ childSandboxReadySignal,
104
+ ({ childWorkflowId, sandboxId }) => {
105
+ const agentName = lazyCreatorAgent.get(childWorkflowId);
106
+ if (agentName && !persistentSandboxes.has(agentName)) {
107
+ persistentSandboxes.set(agentName, sandboxId);
108
+ lazyCreatorAgent.delete(childWorkflowId);
109
+ }
110
+ }
111
+ );
112
+
75
113
  const handler = async (
76
114
  args: SubagentArgs,
77
115
  context: RouterContext
@@ -111,25 +149,74 @@ export function createSubagentHandler<
111
149
 
112
150
  // --- Build sandbox init ---
113
151
  let sandbox: SandboxInit | undefined;
152
+ let sandboxShutdownOverride: SubagentSandboxShutdown | undefined;
153
+ let isLazyCreator = false;
154
+
114
155
  if (sandboxCfg.source === "inherit" && parentSandboxId) {
115
- sandbox = {
116
- mode: "inherit",
117
- sandboxId: parentSandboxId,
118
- };
156
+ if (sandboxCfg.continuation === "fork") {
157
+ sandbox = { mode: "fork", sandboxId: parentSandboxId };
158
+ } else {
159
+ sandbox = { mode: "inherit", sandboxId: parentSandboxId };
160
+ }
119
161
  } else if (sandboxCfg.source === "own") {
120
- const prevSbId = continuationThreadId
121
- ? threadSandboxes.get(continuationThreadId)
122
- : undefined;
123
- if (prevSbId) {
124
- sandbox = { mode: "fork", sandboxId: prevSbId };
162
+ const isLazy = sandboxCfg.init === "once";
163
+
164
+ let baseSandboxId: string | undefined;
165
+ if (isLazy) {
166
+ baseSandboxId = persistentSandboxes.get(config.agentName);
167
+ if (!baseSandboxId) {
168
+ if (persistentSandboxCreating.has(config.agentName)) {
169
+ // Another call is already creating — wait for it to finish
170
+ await condition(() => persistentSandboxes.has(config.agentName));
171
+ baseSandboxId = persistentSandboxes.get(config.agentName);
172
+ } else {
173
+ // We're the first concurrent caller — claim the creator role
174
+ persistentSandboxCreating.add(config.agentName);
175
+ isLazyCreator = true;
176
+ }
177
+ }
178
+ } else if (continuationThreadId) {
179
+ baseSandboxId = threadSandboxes.get(continuationThreadId);
180
+ }
181
+
182
+ if (baseSandboxId) {
183
+ sandbox = {
184
+ mode: sandboxCfg.continuation === "continue" ? "continue" : "fork",
185
+ sandboxId: baseSandboxId,
186
+ };
187
+ }
188
+
189
+ // Ensure the sandbox survives for future continuation/fork:
190
+ // - first lazy call (creator): pause-until-parent-close so parent can clean up
191
+ // - continuation=continue: sandbox must survive for next call
192
+ // - lazy+fork (non-creator): template must survive for future forks
193
+ //
194
+ // Skip the override when the user already configured a *-until-parent-close
195
+ // shutdown — that already guarantees survival.
196
+ const userShutdown = sandboxCfg.shutdown;
197
+ const alreadySurvives =
198
+ userShutdown === "pause-until-parent-close" ||
199
+ userShutdown === "keep-until-parent-close" ||
200
+ userShutdown === "pause" ||
201
+ userShutdown === "keep";
202
+
203
+ const mustSurvive =
204
+ isLazyCreator ||
205
+ sandboxCfg.continuation === "continue" ||
206
+ (isLazy && sandboxCfg.continuation === "fork");
207
+
208
+ if (mustSurvive && !alreadySurvives) {
209
+ sandboxShutdownOverride = isLazyCreator
210
+ ? "pause-until-parent-close"
211
+ : "pause";
125
212
  }
126
- // When no previous sandbox, omit — the child will create its own via sandboxOps
127
213
  }
128
214
 
129
215
  const workflowInput: SubagentWorkflowInput = {
130
216
  ...(thread && { thread }),
131
217
  ...(sandbox && { sandbox }),
132
- ...(sandboxCfg.shutdown && { sandboxShutdown: sandboxCfg.shutdown }),
218
+ sandboxShutdown:
219
+ sandboxShutdownOverride ?? sandboxCfg.shutdown ?? undefined,
133
220
  };
134
221
 
135
222
  const resolvedContext =
@@ -148,6 +235,10 @@ export function createSubagentHandler<
148
235
  taskQueue: config.taskQueue ?? parentTaskQueue,
149
236
  };
150
237
 
238
+ if (isLazyCreator) {
239
+ lazyCreatorAgent.set(childWorkflowId, config.agentName);
240
+ }
241
+
151
242
  log.info("subagent spawned", {
152
243
  subagent: config.agentName,
153
244
  childWorkflowId,
@@ -157,14 +248,18 @@ export function createSubagentHandler<
157
248
 
158
249
  const childHandle = await startChild(config.workflow, childOpts);
159
250
 
160
- const effectiveShutdown = sandboxCfg.shutdown ?? "destroy";
161
- const shouldDeferDestroy =
162
- effectiveShutdown === "pause-until-parent-close" &&
163
- (sandboxCfg.source === "own" ||
164
- (allowsContinuation && sandboxCfg.source !== "inherit"));
165
-
166
- if (shouldDeferDestroy) {
167
- pendingDestroys.set(childWorkflowId, childHandle);
251
+ // Track child handles that need signaling at parent shutdown.
252
+ const effectiveShutdown =
253
+ sandboxShutdownOverride ?? sandboxCfg.shutdown ?? "destroy";
254
+
255
+ if (
256
+ effectiveShutdown === "pause-until-parent-close" ||
257
+ effectiveShutdown === "keep-until-parent-close"
258
+ ) {
259
+ const key = isLazyCreator
260
+ ? `persistent:${config.agentName}`
261
+ : childWorkflowId;
262
+ pendingDestroys.set(key, childHandle);
168
263
  }
169
264
 
170
265
  // Wait for signal from child; race with child completion to propagate failures
@@ -205,8 +300,23 @@ export function createSubagentHandler<
205
300
  metadata,
206
301
  } = childResult;
207
302
 
208
- if (allowsContinuation && childSandboxId && childThreadId) {
209
- threadSandboxes.set(childThreadId, childSandboxId);
303
+ // Store sandbox ID for future continuation/fork
304
+ if (childSandboxId) {
305
+ if (
306
+ sandboxCfg.source === "own" &&
307
+ sandboxCfg.init === "once" &&
308
+ !persistentSandboxes.has(config.agentName)
309
+ ) {
310
+ // Fallback: signal may have already set this via childSandboxReadySignal
311
+ persistentSandboxes.set(config.agentName, childSandboxId);
312
+ } else if (allowsContinuation && childThreadId) {
313
+ threadSandboxes.set(childThreadId, childSandboxId);
314
+ }
315
+ }
316
+
317
+ if (isLazyCreator) {
318
+ persistentSandboxCreating.delete(config.agentName);
319
+ lazyCreatorAgent.delete(childWorkflowId);
210
320
  }
211
321
 
212
322
  if (!toolResponse) {
@@ -17,4 +17,8 @@ export { createSubagentHandler } from "./handler";
17
17
  export { defineSubagent } from "./define";
18
18
  export { defineSubagentWorkflow } from "./workflow";
19
19
  export { buildSubagentRegistration } from "./register";
20
- export { childResultSignal, destroySandboxSignal } from "./signals";
20
+ export {
21
+ childResultSignal,
22
+ childSandboxReadySignal,
23
+ destroySandboxSignal,
24
+ } from "./signals";
@@ -1,8 +1,15 @@
1
1
  import { defineSignal } from "@temporalio/workflow";
2
- import type { ChildResultSignalPayload } from "./types";
2
+ import type {
3
+ ChildResultSignalPayload,
4
+ ChildSandboxReadySignalPayload,
5
+ } from "./types";
3
6
 
4
7
  export const childResultSignal =
5
8
  defineSignal<[ChildResultSignalPayload]>("childResult");
6
9
 
10
+ /** Sent by a child workflow as soon as its sandbox is created, before the agent loop starts. */
11
+ export const childSandboxReadySignal =
12
+ defineSignal<[ChildSandboxReadySignalPayload]>("childSandboxReady");
13
+
7
14
  /** Sent by the parent to tell a subagent it may destroy its sandbox. */
8
15
  export const destroySandboxSignal = defineSignal("destroySandbox");