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
@@ -1,8 +1,6 @@
1
1
  import { describe, expect, it, vi, afterEach } from "vitest";
2
2
  import { z } from "zod";
3
3
 
4
- const capturedSignalHandlers = new Map<unknown, (...args: unknown[]) => void>();
5
-
6
4
  let nextStartChildResult: ((prompt: string) => unknown) | null = null;
7
5
 
8
6
  vi.mock("@temporalio/workflow", () => {
@@ -34,15 +32,11 @@ vi.mock("@temporalio/workflow", () => {
34
32
  parent: { workflowId: "parent-wf-1" },
35
33
  }),
36
34
  defineSignal: vi.fn((name: string) => ({ __signal: true, name })),
37
- setHandler: vi.fn(
38
- (signal: unknown, handler: (...a: unknown[]) => void) => {
39
- capturedSignalHandlers.set(signal, handler);
40
- }
41
- ),
35
+ setHandler: vi.fn(),
42
36
  condition: vi.fn(async (fn: () => boolean) => {
43
37
  if (!fn()) throw new Error("condition predicate was not satisfied");
44
38
  }),
45
- startChild: vi.fn(
39
+ executeChild: vi.fn(
46
40
  async (
47
41
  _workflow: unknown,
48
42
  opts: { workflowId: string; args: unknown[] }
@@ -57,17 +51,7 @@ vi.mock("@temporalio/workflow", () => {
57
51
  usage: { inputTokens: 100, outputTokens: 50 },
58
52
  };
59
53
 
60
- for (const [signal, handler] of capturedSignalHandlers.entries()) {
61
- if ((signal as { name?: string }).name === "childResult") {
62
- handler({ childWorkflowId: opts.workflowId, result });
63
- }
64
- }
65
-
66
- return {
67
- signal: vi.fn(),
68
- result: () => Promise.resolve(result),
69
- workflowId: opts.workflowId,
70
- };
54
+ return result;
71
55
  }
72
56
  ),
73
57
  getExternalWorkflowHandle: vi.fn((_id: string) => ({
@@ -104,7 +88,6 @@ import type {
104
88
  } from "./types";
105
89
  afterEach(() => {
106
90
  nextStartChildResult = null;
107
- capturedSignalHandlers.clear();
108
91
  });
109
92
 
110
93
  function mockWorkflow(name?: string): SubagentWorkflow {
@@ -117,6 +100,27 @@ function mockWorkflow(name?: string): SubagentWorkflow {
117
100
  return fn as SubagentWorkflow;
118
101
  }
119
102
 
103
+ function makeMockSandboxOps() {
104
+ return {
105
+ createSandbox: vi.fn(),
106
+ destroySandbox: vi.fn(),
107
+ pauseSandbox: vi.fn(),
108
+ resumeSandbox: vi.fn(),
109
+ snapshotSandbox: vi.fn(),
110
+ restoreSandbox: vi.fn(),
111
+ deleteSandboxSnapshot: vi.fn(),
112
+ forkSandbox: vi.fn(),
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Default no-op sandbox proxy factory — satisfies the runtime check that
118
+ * every sandbox-using subagent must declare `sandbox.proxy`. Tests that
119
+ * need to assert on destroy/cleanup create their own mock ops and wire
120
+ * them through `sandbox: { ..., proxy: () => opsMock }`.
121
+ */
122
+ const noopSandboxProxy = () => makeMockSandboxOps();
123
+
120
124
  // ---------------------------------------------------------------------------
121
125
  // createSubagentTool
122
126
  // ---------------------------------------------------------------------------
@@ -370,14 +374,18 @@ describe("createSubagentHandler", () => {
370
374
  });
371
375
 
372
376
  it("passes sandbox inherit to child when sandbox is inherit", async () => {
373
- const { startChild } = await import("@temporalio/workflow");
374
- const startMock = startChild as ReturnType<typeof vi.fn>;
377
+ const { executeChild } = await import("@temporalio/workflow");
378
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
375
379
 
376
380
  const inheritSubagent: SubagentConfig = {
377
381
  agentName: "inherit-agent",
378
382
  description: "Inherits sandbox",
379
383
  workflow: mockWorkflow(),
380
- sandbox: { source: "inherit", continuation: "continue" },
384
+ sandbox: {
385
+ source: "inherit",
386
+ continuation: "continue",
387
+ proxy: noopSandboxProxy,
388
+ },
381
389
  };
382
390
 
383
391
  const { handler } = createSubagentHandler([inheritSubagent]);
@@ -392,8 +400,8 @@ describe("createSubagentHandler", () => {
392
400
  }
393
401
  );
394
402
 
395
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
396
- if (!lastCall) throw new Error("expected startChild call");
403
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
404
+ if (!lastCall) throw new Error("expected executeChild call");
397
405
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
398
406
  expect(workflowInput.sandbox).toEqual({
399
407
  mode: "inherit",
@@ -406,7 +414,11 @@ describe("createSubagentHandler", () => {
406
414
  agentName: "inherit-agent",
407
415
  description: "Inherits sandbox",
408
416
  workflow: mockWorkflow(),
409
- sandbox: { source: "inherit", continuation: "continue" },
417
+ sandbox: {
418
+ source: "inherit",
419
+ continuation: "continue",
420
+ proxy: noopSandboxProxy,
421
+ },
410
422
  };
411
423
 
412
424
  const { handler } = createSubagentHandler([inheritSubagent]);
@@ -420,14 +432,14 @@ describe("createSubagentHandler", () => {
420
432
  });
421
433
 
422
434
  it("does not pass sandboxId to child when sandbox is own (first call)", async () => {
423
- const { startChild } = await import("@temporalio/workflow");
424
- const startMock = startChild as ReturnType<typeof vi.fn>;
435
+ const { executeChild } = await import("@temporalio/workflow");
436
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
425
437
 
426
438
  const ownSubagent: SubagentConfig = {
427
439
  agentName: "own-agent",
428
440
  description: "Own sandbox",
429
441
  workflow: mockWorkflow(),
430
- sandbox: { source: "own", continuation: "fork" },
442
+ sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
431
443
  };
432
444
 
433
445
  const { handler } = createSubagentHandler([ownSubagent]);
@@ -442,15 +454,15 @@ describe("createSubagentHandler", () => {
442
454
  }
443
455
  );
444
456
 
445
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
446
- if (!lastCall) throw new Error("expected startChild call");
457
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
458
+ if (!lastCall) throw new Error("expected executeChild call");
447
459
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
448
460
  expect(workflowInput.sandbox).toBeUndefined();
449
461
  });
450
462
 
451
463
  it("resolves context function at invocation time", async () => {
452
- const { startChild } = await import("@temporalio/workflow");
453
- const startMock = startChild as ReturnType<typeof vi.fn>;
464
+ const { executeChild } = await import("@temporalio/workflow");
465
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
454
466
 
455
467
  let counter = 0;
456
468
  const dynamicSubagent: SubagentConfig = {
@@ -470,15 +482,15 @@ describe("createSubagentHandler", () => {
470
482
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
471
483
  );
472
484
 
473
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
474
- if (!lastCall) throw new Error("expected startChild call");
485
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
486
+ if (!lastCall) throw new Error("expected executeChild call");
475
487
  const context = lastCall[1].args[2] as Record<string, unknown>;
476
488
  expect(context).toEqual({ invocation: 1 });
477
489
  });
478
490
 
479
491
  it("passes static context unchanged", async () => {
480
- const { startChild } = await import("@temporalio/workflow");
481
- const startMock = startChild as ReturnType<typeof vi.fn>;
492
+ const { executeChild } = await import("@temporalio/workflow");
493
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
482
494
 
483
495
  const staticSubagent: SubagentConfig = {
484
496
  agentName: "static-ctx",
@@ -494,21 +506,21 @@ describe("createSubagentHandler", () => {
494
506
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
495
507
  );
496
508
 
497
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
498
- if (!lastCall) throw new Error("expected startChild call");
509
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
510
+ if (!lastCall) throw new Error("expected executeChild call");
499
511
  const context = lastCall[1].args[2] as Record<string, unknown>;
500
512
  expect(context).toEqual({ key: "value" });
501
513
  });
502
514
 
503
515
  it("does not pass sandbox init when sandbox is own without prior sandbox", async () => {
504
- const { startChild } = await import("@temporalio/workflow");
505
- const startMock = startChild as ReturnType<typeof vi.fn>;
516
+ const { executeChild } = await import("@temporalio/workflow");
517
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
506
518
 
507
519
  const ownSubagent: SubagentConfig = {
508
520
  agentName: "own-agent",
509
521
  description: "Own sandbox",
510
522
  workflow: mockWorkflow(),
511
- sandbox: { source: "own", continuation: "fork" },
523
+ sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
512
524
  };
513
525
 
514
526
  const { handler } = createSubagentHandler([ownSubagent]);
@@ -523,8 +535,8 @@ describe("createSubagentHandler", () => {
523
535
  }
524
536
  );
525
537
 
526
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
527
- if (!lastCall) throw new Error("expected startChild call");
538
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
539
+ if (!lastCall) throw new Error("expected executeChild call");
528
540
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
529
541
  expect(workflowInput.sandbox).toBeUndefined();
530
542
  });
@@ -532,8 +544,8 @@ describe("createSubagentHandler", () => {
532
544
  // --- Thread mode ---
533
545
 
534
546
  it("passes thread fork when thread is fork and threadId provided", async () => {
535
- const { startChild } = await import("@temporalio/workflow");
536
- const startMock = startChild as ReturnType<typeof vi.fn>;
547
+ const { executeChild } = await import("@temporalio/workflow");
548
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
537
549
 
538
550
  const contSubagent: SubagentConfig = {
539
551
  agentName: "cont",
@@ -554,8 +566,8 @@ describe("createSubagentHandler", () => {
554
566
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
555
567
  );
556
568
 
557
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
558
- if (!lastCall) throw new Error("expected startChild call");
569
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
570
+ if (!lastCall) throw new Error("expected executeChild call");
559
571
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
560
572
  expect(workflowInput.thread).toEqual({
561
573
  mode: "fork",
@@ -564,8 +576,8 @@ describe("createSubagentHandler", () => {
564
576
  });
565
577
 
566
578
  it("passes thread continue when thread is continue", async () => {
567
- const { startChild } = await import("@temporalio/workflow");
568
- const startMock = startChild as ReturnType<typeof vi.fn>;
579
+ const { executeChild } = await import("@temporalio/workflow");
580
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
569
581
 
570
582
  const contSubagent: SubagentConfig = {
571
583
  agentName: "cont-mode",
@@ -586,8 +598,8 @@ describe("createSubagentHandler", () => {
586
598
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
587
599
  );
588
600
 
589
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
590
- if (!lastCall) throw new Error("expected startChild call");
601
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
602
+ if (!lastCall) throw new Error("expected executeChild call");
591
603
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
592
604
  expect(workflowInput.thread).toEqual({
593
605
  mode: "continue",
@@ -596,8 +608,8 @@ describe("createSubagentHandler", () => {
596
608
  });
597
609
 
598
610
  it("does not pass thread when thread is new", async () => {
599
- const { startChild } = await import("@temporalio/workflow");
600
- const startMock = startChild as ReturnType<typeof vi.fn>;
611
+ const { executeChild } = await import("@temporalio/workflow");
612
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
601
613
 
602
614
  const noContSubagent: SubagentConfig = {
603
615
  agentName: "no-cont",
@@ -617,8 +629,8 @@ describe("createSubagentHandler", () => {
617
629
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
618
630
  );
619
631
 
620
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
621
- if (!lastCall) throw new Error("expected startChild call");
632
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
633
+ if (!lastCall) throw new Error("expected executeChild call");
622
634
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
623
635
  expect(workflowInput.thread).toBeUndefined();
624
636
  });
@@ -626,8 +638,8 @@ describe("createSubagentHandler", () => {
626
638
  // --- Sandbox continuation ---
627
639
 
628
640
  it("does not pass sandbox when thread is fork (own sandbox)", async () => {
629
- const { startChild } = await import("@temporalio/workflow");
630
- const startMock = startChild as ReturnType<typeof vi.fn>;
641
+ const { executeChild } = await import("@temporalio/workflow");
642
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
631
643
 
632
644
  const contSandboxSubagent: SubagentConfig = {
633
645
  agentName: "sb-cont",
@@ -648,15 +660,15 @@ describe("createSubagentHandler", () => {
648
660
  }
649
661
  );
650
662
 
651
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
652
- if (!lastCall) throw new Error("expected startChild call");
663
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
664
+ if (!lastCall) throw new Error("expected executeChild call");
653
665
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
654
666
  expect(workflowInput.sandbox).toBeUndefined();
655
667
  });
656
668
 
657
669
  it("tracks sandbox ID and passes sandbox fork on continuation", async () => {
658
- const { startChild } = await import("@temporalio/workflow");
659
- const startMock = startChild as ReturnType<typeof vi.fn>;
670
+ const { executeChild } = await import("@temporalio/workflow");
671
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
660
672
 
661
673
  nextStartChildResult = () => ({
662
674
  toolResponse: "first run done",
@@ -670,7 +682,7 @@ describe("createSubagentHandler", () => {
670
682
  description: "Sandbox continuation",
671
683
  workflow: mockWorkflow(),
672
684
  thread: "fork",
673
- sandbox: { source: "own", continuation: "fork" },
685
+ sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
674
686
  };
675
687
 
676
688
  const { handler } = createSubagentHandler([contSandboxSubagent]);
@@ -697,8 +709,8 @@ describe("createSubagentHandler", () => {
697
709
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
698
710
  );
699
711
 
700
- const secondCall = startMock.mock.calls[startMock.mock.calls.length - 1];
701
- if (!secondCall) throw new Error("expected second startChild call");
712
+ const secondCall = execMock.mock.calls[execMock.mock.calls.length - 1];
713
+ if (!secondCall) throw new Error("expected second executeChild call");
702
714
  const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
703
715
  expect(workflowInput.thread).toEqual({
704
716
  mode: "fork",
@@ -711,8 +723,8 @@ describe("createSubagentHandler", () => {
711
723
  });
712
724
 
713
725
  it("does not pass sandbox fork without thread continuation", async () => {
714
- const { startChild } = await import("@temporalio/workflow");
715
- const startMock = startChild as ReturnType<typeof vi.fn>;
726
+ const { executeChild } = await import("@temporalio/workflow");
727
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
716
728
 
717
729
  nextStartChildResult = () => ({
718
730
  toolResponse: "done",
@@ -726,7 +738,7 @@ describe("createSubagentHandler", () => {
726
738
  description: "Sandbox continuation",
727
739
  workflow: mockWorkflow(),
728
740
  thread: "fork",
729
- sandbox: { source: "own", continuation: "fork" },
741
+ sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
730
742
  };
731
743
 
732
744
  const { handler } = createSubagentHandler([contSandboxSubagent]);
@@ -748,17 +760,14 @@ describe("createSubagentHandler", () => {
748
760
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
749
761
  );
750
762
 
751
- const secondCall = startMock.mock.calls[startMock.mock.calls.length - 1];
752
- if (!secondCall) throw new Error("expected startChild call");
763
+ const secondCall = execMock.mock.calls[execMock.mock.calls.length - 1];
764
+ if (!secondCall) throw new Error("expected executeChild call");
753
765
  const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
754
766
  expect(workflowInput.sandbox).toBeUndefined();
755
767
  expect(workflowInput.thread).toBeUndefined();
756
768
  });
757
769
 
758
- it("does not signal destroy for fork-mode subagent without pause-until-parent-close", async () => {
759
- const { startChild } = await import("@temporalio/workflow");
760
- const startMock = startChild as ReturnType<typeof vi.fn>;
761
-
770
+ it("does not destroy fork-mode subagent sandbox without pause-until-parent-close", async () => {
762
771
  const contSandboxSubagent: SubagentConfig = {
763
772
  agentName: "sb-cont",
764
773
  description: "Sandbox continuation",
@@ -775,25 +784,16 @@ describe("createSubagentHandler", () => {
775
784
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
776
785
  );
777
786
 
778
- const lastResult = startMock.mock.results.at(-1);
779
- if (!lastResult) throw new Error("expected startChild call");
780
- const childHandle = await lastResult.value;
781
- childHandle.signal.mockClear();
782
-
783
- await destroySubagentSandboxes();
784
-
785
- expect(childHandle.signal).not.toHaveBeenCalled();
787
+ await expect(destroySubagentSandboxes()).resolves.toBeUndefined();
786
788
  });
787
789
 
788
- it("does not signal destroy for sandbox=own with default shutdown", async () => {
789
- const { startChild } = await import("@temporalio/workflow");
790
- const startMock = startChild as ReturnType<typeof vi.fn>;
791
-
790
+ it("does not destroy sandbox=own with default shutdown", async () => {
791
+ const opsMock = makeMockSandboxOps();
792
792
  const ownSubagent: SubagentConfig = {
793
793
  agentName: "own-agent",
794
794
  description: "Own sandbox",
795
795
  workflow: mockWorkflow(),
796
- sandbox: { source: "own", continuation: "fork" },
796
+ sandbox: { source: "own", continuation: "fork", proxy: () => opsMock },
797
797
  };
798
798
 
799
799
  const { handler, destroySubagentSandboxes } = createSubagentHandler([
@@ -805,20 +805,20 @@ describe("createSubagentHandler", () => {
805
805
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
806
806
  );
807
807
 
808
- const lastResult = startMock.mock.results.at(-1);
809
- if (!lastResult) throw new Error("expected startChild call");
810
- const childHandle = await lastResult.value;
811
- childHandle.signal.mockClear();
812
-
813
808
  await destroySubagentSandboxes();
814
809
 
815
- expect(childHandle.signal).not.toHaveBeenCalled();
810
+ expect(opsMock.destroySandbox).not.toHaveBeenCalled();
816
811
  });
817
812
 
818
- it("signals destroy for sandbox=own with pause-until-parent-close shutdown", async () => {
819
- const { startChild } = await import("@temporalio/workflow");
820
- const startMock = startChild as ReturnType<typeof vi.fn>;
813
+ it("destroys sandbox via sandbox.proxy for own + pause-until-parent-close", async () => {
814
+ nextStartChildResult = () => ({
815
+ toolResponse: "done",
816
+ data: null,
817
+ threadId: "child-t",
818
+ sandboxId: "child-sb-99",
819
+ });
821
820
 
821
+ const opsMock = makeMockSandboxOps();
822
822
  const ownSubagent: SubagentConfig = {
823
823
  agentName: "own-agent",
824
824
  description: "Own sandbox",
@@ -827,6 +827,7 @@ describe("createSubagentHandler", () => {
827
827
  source: "own",
828
828
  continuation: "fork",
829
829
  shutdown: "pause-until-parent-close",
830
+ proxy: () => opsMock,
830
831
  },
831
832
  };
832
833
 
@@ -841,21 +842,20 @@ describe("createSubagentHandler", () => {
841
842
 
842
843
  await destroySubagentSandboxes();
843
844
 
844
- const lastResult = startMock.mock.results.at(-1);
845
- if (!lastResult) throw new Error("expected startChild call");
846
- const childHandle = await lastResult.value;
847
- expect(childHandle.signal).toHaveBeenCalled();
845
+ expect(opsMock.destroySandbox).toHaveBeenCalledWith("child-sb-99");
848
846
  });
849
847
 
850
- it("does not signal destroy for inherit subagents", async () => {
851
- const { startChild } = await import("@temporalio/workflow");
852
- const startMock = startChild as ReturnType<typeof vi.fn>;
853
-
848
+ it("does not destroy inherit subagents' sandbox", async () => {
849
+ const opsMock = makeMockSandboxOps();
854
850
  const inheritSubagent: SubagentConfig = {
855
851
  agentName: "inherit-agent",
856
852
  description: "Inherits sandbox",
857
853
  workflow: mockWorkflow(),
858
- sandbox: { source: "inherit", continuation: "continue" },
854
+ sandbox: {
855
+ source: "inherit",
856
+ continuation: "continue",
857
+ proxy: () => opsMock,
858
+ },
859
859
  };
860
860
 
861
861
  const { handler, destroySubagentSandboxes } = createSubagentHandler([
@@ -872,19 +872,14 @@ describe("createSubagentHandler", () => {
872
872
  }
873
873
  );
874
874
 
875
- const lastResult = startMock.mock.results.at(-1);
876
- if (!lastResult) throw new Error("expected startChild call");
877
- const childHandle = await lastResult.value;
878
- childHandle.signal.mockClear();
879
-
880
875
  await destroySubagentSandboxes();
881
876
 
882
- expect(childHandle.signal).not.toHaveBeenCalled();
877
+ expect(opsMock.destroySandbox).not.toHaveBeenCalled();
883
878
  });
884
879
 
885
880
  it("does not pass sandboxId when sandbox is none (default)", async () => {
886
- const { startChild } = await import("@temporalio/workflow");
887
- const startMock = startChild as ReturnType<typeof vi.fn>;
881
+ const { executeChild } = await import("@temporalio/workflow");
882
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
888
883
 
889
884
  const noneSubagent: SubagentConfig = {
890
885
  agentName: "none-agent",
@@ -904,15 +899,15 @@ describe("createSubagentHandler", () => {
904
899
  }
905
900
  );
906
901
 
907
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
908
- if (!lastCall) throw new Error("expected startChild call");
902
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
903
+ if (!lastCall) throw new Error("expected executeChild call");
909
904
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
910
905
  expect(workflowInput.sandbox).toBeUndefined();
911
906
  });
912
907
 
913
908
  it("does not pass sandboxId when sandbox is explicitly none", async () => {
914
- const { startChild } = await import("@temporalio/workflow");
915
- const startMock = startChild as ReturnType<typeof vi.fn>;
909
+ const { executeChild } = await import("@temporalio/workflow");
910
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
916
911
 
917
912
  const noneSubagent: SubagentConfig = {
918
913
  agentName: "none-agent",
@@ -933,16 +928,13 @@ describe("createSubagentHandler", () => {
933
928
  }
934
929
  );
935
930
 
936
- const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
937
- if (!lastCall) throw new Error("expected startChild call");
931
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
932
+ if (!lastCall) throw new Error("expected executeChild call");
938
933
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
939
934
  expect(workflowInput.sandbox).toBeUndefined();
940
935
  });
941
936
 
942
- it("does not signal destroy for none subagents", async () => {
943
- const { startChild } = await import("@temporalio/workflow");
944
- const startMock = startChild as ReturnType<typeof vi.fn>;
945
-
937
+ it("destroySubagentSandboxes is a no-op for none subagents", async () => {
946
938
  const noneSubagent: SubagentConfig = {
947
939
  agentName: "none-agent",
948
940
  description: "No sandbox",
@@ -959,27 +951,24 @@ describe("createSubagentHandler", () => {
959
951
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
960
952
  );
961
953
 
962
- const lastResult = startMock.mock.results.at(-1);
963
- if (!lastResult) throw new Error("expected startChild call");
964
- const childHandle = await lastResult.value;
965
- childHandle.signal.mockClear();
966
-
967
- await destroySubagentSandboxes();
968
-
969
- expect(childHandle.signal).not.toHaveBeenCalled();
954
+ await expect(destroySubagentSandboxes()).resolves.toBeUndefined();
970
955
  });
971
956
 
972
957
  // --- inherit + continuation: fork ---
973
958
 
974
959
  it("forks from parent sandbox when inherit + continuation=fork", async () => {
975
- const { startChild } = await import("@temporalio/workflow");
976
- const startMock = startChild as ReturnType<typeof vi.fn>;
960
+ const { executeChild } = await import("@temporalio/workflow");
961
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
977
962
 
978
963
  const config: SubagentConfig = {
979
964
  agentName: "inherit-fork",
980
965
  description: "Inherit fork",
981
966
  workflow: mockWorkflow(),
982
- sandbox: { source: "inherit", continuation: "fork" },
967
+ sandbox: {
968
+ source: "inherit",
969
+ continuation: "fork",
970
+ proxy: noopSandboxProxy,
971
+ },
983
972
  };
984
973
 
985
974
  const { handler } = createSubagentHandler([config]);
@@ -994,8 +983,8 @@ describe("createSubagentHandler", () => {
994
983
  }
995
984
  );
996
985
 
997
- const lastCall = startMock.mock.calls.at(-1);
998
- if (!lastCall) throw new Error("expected startChild call");
986
+ const lastCall = execMock.mock.calls.at(-1);
987
+ if (!lastCall) throw new Error("expected executeChild call");
999
988
  const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
1000
989
  expect(workflowInput.sandbox).toEqual({
1001
990
  mode: "fork",
@@ -1006,8 +995,8 @@ describe("createSubagentHandler", () => {
1006
995
  // --- own + continuation: continue ---
1007
996
 
1008
997
  it("passes sandbox continue on thread continuation with continuation=continue", async () => {
1009
- const { startChild } = await import("@temporalio/workflow");
1010
- const startMock = startChild as ReturnType<typeof vi.fn>;
998
+ const { executeChild } = await import("@temporalio/workflow");
999
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1011
1000
 
1012
1001
  nextStartChildResult = () => ({
1013
1002
  toolResponse: "first",
@@ -1021,7 +1010,11 @@ describe("createSubagentHandler", () => {
1021
1010
  description: "Own continue",
1022
1011
  workflow: mockWorkflow(),
1023
1012
  thread: "continue",
1024
- sandbox: { source: "own", continuation: "continue" },
1013
+ sandbox: {
1014
+ source: "own",
1015
+ continuation: "continue",
1016
+ proxy: noopSandboxProxy,
1017
+ },
1025
1018
  };
1026
1019
 
1027
1020
  const { handler } = createSubagentHandler([config]);
@@ -1048,8 +1041,8 @@ describe("createSubagentHandler", () => {
1048
1041
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1049
1042
  );
1050
1043
 
1051
- const secondCall = startMock.mock.calls.at(-1);
1052
- if (!secondCall) throw new Error("expected startChild call");
1044
+ const secondCall = execMock.mock.calls.at(-1);
1045
+ if (!secondCall) throw new Error("expected executeChild call");
1053
1046
  const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
1054
1047
  expect(workflowInput.sandbox).toEqual({
1055
1048
  mode: "continue",
@@ -1061,8 +1054,8 @@ describe("createSubagentHandler", () => {
1061
1054
  // --- own + init: once + continuation: fork ---
1062
1055
 
1063
1056
  it("stores sandbox on first call and forks from it on second call (init=once, continuation=fork)", async () => {
1064
- const { startChild } = await import("@temporalio/workflow");
1065
- const startMock = startChild as ReturnType<typeof vi.fn>;
1057
+ const { executeChild } = await import("@temporalio/workflow");
1058
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1066
1059
 
1067
1060
  nextStartChildResult = () => ({
1068
1061
  toolResponse: "first",
@@ -1075,7 +1068,12 @@ describe("createSubagentHandler", () => {
1075
1068
  agentName: "lazy-fork",
1076
1069
  description: "Lazy fork",
1077
1070
  workflow: mockWorkflow(),
1078
- sandbox: { source: "own", init: "once", continuation: "fork" },
1071
+ sandbox: {
1072
+ source: "own",
1073
+ init: "once",
1074
+ continuation: "fork",
1075
+ proxy: noopSandboxProxy,
1076
+ },
1079
1077
  };
1080
1078
 
1081
1079
  const { handler } = createSubagentHandler([config]);
@@ -1086,8 +1084,8 @@ describe("createSubagentHandler", () => {
1086
1084
  );
1087
1085
 
1088
1086
  // First call: no sandbox init (child creates fresh), forced pause-until-parent-close
1089
- const firstCall = startMock.mock.calls.at(-1);
1090
- if (!firstCall) throw new Error("expected startChild call");
1087
+ const firstCall = execMock.mock.calls.at(-1);
1088
+ if (!firstCall) throw new Error("expected executeChild call");
1091
1089
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1092
1090
  expect(firstInput.sandbox).toBeUndefined();
1093
1091
  expect(firstInput.sandboxShutdown).toBe("pause-until-parent-close");
@@ -1105,8 +1103,8 @@ describe("createSubagentHandler", () => {
1105
1103
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1106
1104
  );
1107
1105
 
1108
- const secondCall = startMock.mock.calls.at(-1);
1109
- if (!secondCall) throw new Error("expected startChild call");
1106
+ const secondCall = execMock.mock.calls.at(-1);
1107
+ if (!secondCall) throw new Error("expected executeChild call");
1110
1108
  const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1111
1109
  expect(secondInput.sandbox).toEqual({
1112
1110
  mode: "fork",
@@ -1117,8 +1115,8 @@ describe("createSubagentHandler", () => {
1117
1115
  // --- own + init: once + continuation: continue ---
1118
1116
 
1119
1117
  it("stores sandbox on first call and continues it on second call (init=once, continuation=continue)", async () => {
1120
- const { startChild } = await import("@temporalio/workflow");
1121
- const startMock = startChild as ReturnType<typeof vi.fn>;
1118
+ const { executeChild } = await import("@temporalio/workflow");
1119
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1122
1120
 
1123
1121
  nextStartChildResult = () => ({
1124
1122
  toolResponse: "first",
@@ -1131,7 +1129,12 @@ describe("createSubagentHandler", () => {
1131
1129
  agentName: "lazy-cont",
1132
1130
  description: "Lazy continue",
1133
1131
  workflow: mockWorkflow(),
1134
- sandbox: { source: "own", init: "once", continuation: "continue" },
1132
+ sandbox: {
1133
+ source: "own",
1134
+ init: "once",
1135
+ continuation: "continue",
1136
+ proxy: noopSandboxProxy,
1137
+ },
1135
1138
  };
1136
1139
 
1137
1140
  const { handler } = createSubagentHandler([config]);
@@ -1153,8 +1156,8 @@ describe("createSubagentHandler", () => {
1153
1156
  { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1154
1157
  );
1155
1158
 
1156
- const secondCall = startMock.mock.calls.at(-1);
1157
- if (!secondCall) throw new Error("expected startChild call");
1159
+ const secondCall = execMock.mock.calls.at(-1);
1160
+ if (!secondCall) throw new Error("expected executeChild call");
1158
1161
  const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1159
1162
  expect(secondInput.sandbox).toEqual({
1160
1163
  mode: "continue",
@@ -1165,10 +1168,7 @@ describe("createSubagentHandler", () => {
1165
1168
 
1166
1169
  // --- init: once cleanup ---
1167
1170
 
1168
- it("adds first-call child handle to pendingDestroys for init=once", async () => {
1169
- const { startChild } = await import("@temporalio/workflow");
1170
- const startMock = startChild as ReturnType<typeof vi.fn>;
1171
-
1171
+ it("destroys the persistent sandbox for init=once at parent shutdown", async () => {
1172
1172
  nextStartChildResult = () => ({
1173
1173
  toolResponse: "done",
1174
1174
  data: null,
@@ -1176,11 +1176,17 @@ describe("createSubagentHandler", () => {
1176
1176
  sandboxId: "persistent-sb",
1177
1177
  });
1178
1178
 
1179
+ const opsMock = makeMockSandboxOps();
1179
1180
  const config: SubagentConfig = {
1180
1181
  agentName: "lazy-cleanup",
1181
1182
  description: "Lazy cleanup",
1182
1183
  workflow: mockWorkflow(),
1183
- sandbox: { source: "own", init: "once", continuation: "fork" },
1184
+ sandbox: {
1185
+ source: "own",
1186
+ init: "once",
1187
+ continuation: "fork",
1188
+ proxy: () => opsMock,
1189
+ },
1184
1190
  };
1185
1191
 
1186
1192
  const { handler, destroySubagentSandboxes } = createSubagentHandler([
@@ -1194,10 +1200,7 @@ describe("createSubagentHandler", () => {
1194
1200
 
1195
1201
  await destroySubagentSandboxes();
1196
1202
 
1197
- const lastResult = startMock.mock.results.at(-1);
1198
- if (!lastResult) throw new Error("expected startChild call");
1199
- const childHandle = await lastResult.value;
1200
- expect(childHandle.signal).toHaveBeenCalled();
1203
+ expect(opsMock.destroySandbox).toHaveBeenCalledWith("persistent-sb");
1201
1204
  });
1202
1205
 
1203
1206
  it("returns sandboxId in response when child creates a sandbox", async () => {
@@ -1212,7 +1215,7 @@ describe("createSubagentHandler", () => {
1212
1215
  agentName: "own-agent",
1213
1216
  description: "Own sandbox",
1214
1217
  workflow: mockWorkflow(),
1215
- sandbox: { source: "own", continuation: "fork" },
1218
+ sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
1216
1219
  };
1217
1220
 
1218
1221
  const { handler } = createSubagentHandler([ownSubagent]);
@@ -1325,10 +1328,15 @@ describe("createSubagentHandler", () => {
1325
1328
 
1326
1329
  // --- keep-until-parent-close ---
1327
1330
 
1328
- it("signals destroy for sandbox=own with keep-until-parent-close shutdown", async () => {
1329
- const { startChild } = await import("@temporalio/workflow");
1330
- const startMock = startChild as ReturnType<typeof vi.fn>;
1331
+ it("destroys sandbox for own + keep-until-parent-close", async () => {
1332
+ nextStartChildResult = () => ({
1333
+ toolResponse: "done",
1334
+ data: null,
1335
+ threadId: "child-t",
1336
+ sandboxId: "child-sb-77",
1337
+ });
1331
1338
 
1339
+ const opsMock = makeMockSandboxOps();
1332
1340
  const ownSubagent: SubagentConfig = {
1333
1341
  agentName: "own-keep",
1334
1342
  description: "Own sandbox kept",
@@ -1337,6 +1345,7 @@ describe("createSubagentHandler", () => {
1337
1345
  source: "own",
1338
1346
  continuation: "fork",
1339
1347
  shutdown: "keep-until-parent-close",
1348
+ proxy: () => opsMock,
1340
1349
  },
1341
1350
  };
1342
1351
 
@@ -1351,21 +1360,21 @@ describe("createSubagentHandler", () => {
1351
1360
 
1352
1361
  await destroySubagentSandboxes();
1353
1362
 
1354
- const lastResult = startMock.mock.results.at(-1);
1355
- if (!lastResult) throw new Error("expected startChild call");
1356
- const childHandle = await lastResult.value;
1357
- expect(childHandle.signal).toHaveBeenCalled();
1363
+ expect(opsMock.destroySandbox).toHaveBeenCalledWith("child-sb-77");
1358
1364
  });
1359
1365
 
1360
- it("does not signal destroy for sandbox=own with keep shutdown (without parent-close)", async () => {
1361
- const { startChild } = await import("@temporalio/workflow");
1362
- const startMock = startChild as ReturnType<typeof vi.fn>;
1363
-
1366
+ it("does not destroy sandbox for own + keep (without parent-close)", async () => {
1367
+ const opsMock = makeMockSandboxOps();
1364
1368
  const ownSubagent: SubagentConfig = {
1365
1369
  agentName: "own-keep-plain",
1366
1370
  description: "Own sandbox keep",
1367
1371
  workflow: mockWorkflow(),
1368
- sandbox: { source: "own", continuation: "fork", shutdown: "keep" },
1372
+ sandbox: {
1373
+ source: "own",
1374
+ continuation: "fork",
1375
+ shutdown: "keep",
1376
+ proxy: () => opsMock,
1377
+ },
1369
1378
  };
1370
1379
 
1371
1380
  const { handler, destroySubagentSandboxes } = createSubagentHandler([
@@ -1377,21 +1386,16 @@ describe("createSubagentHandler", () => {
1377
1386
  { threadId: "t", toolCallId: "tc", toolName: "Subagent" }
1378
1387
  );
1379
1388
 
1380
- const lastResult = startMock.mock.results.at(-1);
1381
- if (!lastResult) throw new Error("expected startChild call");
1382
- const childHandle = await lastResult.value;
1383
- childHandle.signal.mockClear();
1384
-
1385
1389
  await destroySubagentSandboxes();
1386
1390
 
1387
- expect(childHandle.signal).not.toHaveBeenCalled();
1391
+ expect(opsMock.destroySandbox).not.toHaveBeenCalled();
1388
1392
  });
1389
1393
 
1390
1394
  // --- mustSurvive does not override user shutdown ---
1391
1395
 
1392
1396
  it("does not override keep-until-parent-close with pause-until-parent-close for init=once", async () => {
1393
- const { startChild } = await import("@temporalio/workflow");
1394
- const startMock = startChild as ReturnType<typeof vi.fn>;
1397
+ const { executeChild } = await import("@temporalio/workflow");
1398
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1395
1399
 
1396
1400
  nextStartChildResult = () => ({
1397
1401
  toolResponse: "first",
@@ -1409,6 +1413,7 @@ describe("createSubagentHandler", () => {
1409
1413
  init: "once",
1410
1414
  continuation: "fork",
1411
1415
  shutdown: "keep-until-parent-close",
1416
+ proxy: noopSandboxProxy,
1412
1417
  },
1413
1418
  };
1414
1419
 
@@ -1419,15 +1424,15 @@ describe("createSubagentHandler", () => {
1419
1424
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1420
1425
  );
1421
1426
 
1422
- const firstCall = startMock.mock.calls.at(-1);
1423
- if (!firstCall) throw new Error("expected startChild call");
1427
+ const firstCall = execMock.mock.calls.at(-1);
1428
+ if (!firstCall) throw new Error("expected executeChild call");
1424
1429
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1425
1430
  expect(firstInput.sandboxShutdown).toBe("keep-until-parent-close");
1426
1431
  });
1427
1432
 
1428
1433
  it("does not override pause with pause-until-parent-close for init=once", async () => {
1429
- const { startChild } = await import("@temporalio/workflow");
1430
- const startMock = startChild as ReturnType<typeof vi.fn>;
1434
+ const { executeChild } = await import("@temporalio/workflow");
1435
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1431
1436
 
1432
1437
  nextStartChildResult = () => ({
1433
1438
  toolResponse: "first",
@@ -1445,6 +1450,7 @@ describe("createSubagentHandler", () => {
1445
1450
  init: "once",
1446
1451
  continuation: "fork",
1447
1452
  shutdown: "pause",
1453
+ proxy: noopSandboxProxy,
1448
1454
  },
1449
1455
  };
1450
1456
 
@@ -1455,15 +1461,15 @@ describe("createSubagentHandler", () => {
1455
1461
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1456
1462
  );
1457
1463
 
1458
- const firstCall = startMock.mock.calls.at(-1);
1459
- if (!firstCall) throw new Error("expected startChild call");
1464
+ const firstCall = execMock.mock.calls.at(-1);
1465
+ if (!firstCall) throw new Error("expected executeChild call");
1460
1466
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1461
1467
  expect(firstInput.sandboxShutdown).toBe("pause");
1462
1468
  });
1463
1469
 
1464
1470
  it("does not override keep with pause for continuation=continue", async () => {
1465
- const { startChild } = await import("@temporalio/workflow");
1466
- const startMock = startChild as ReturnType<typeof vi.fn>;
1471
+ const { executeChild } = await import("@temporalio/workflow");
1472
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1467
1473
 
1468
1474
  nextStartChildResult = () => ({
1469
1475
  toolResponse: "first",
@@ -1477,7 +1483,12 @@ describe("createSubagentHandler", () => {
1477
1483
  description: "Continue keep",
1478
1484
  workflow: mockWorkflow(),
1479
1485
  thread: "continue",
1480
- sandbox: { source: "own", continuation: "continue", shutdown: "keep" },
1486
+ sandbox: {
1487
+ source: "own",
1488
+ continuation: "continue",
1489
+ shutdown: "keep",
1490
+ proxy: noopSandboxProxy,
1491
+ },
1481
1492
  };
1482
1493
 
1483
1494
  const { handler } = createSubagentHandler([config]);
@@ -1487,15 +1498,15 @@ describe("createSubagentHandler", () => {
1487
1498
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1488
1499
  );
1489
1500
 
1490
- const firstCall = startMock.mock.calls.at(-1);
1491
- if (!firstCall) throw new Error("expected startChild call");
1501
+ const firstCall = execMock.mock.calls.at(-1);
1502
+ if (!firstCall) throw new Error("expected executeChild call");
1492
1503
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1493
1504
  expect(firstInput.sandboxShutdown).toBe("keep");
1494
1505
  });
1495
1506
 
1496
1507
  it("still overrides destroy with pause for continuation=continue", async () => {
1497
- const { startChild } = await import("@temporalio/workflow");
1498
- const startMock = startChild as ReturnType<typeof vi.fn>;
1508
+ const { executeChild } = await import("@temporalio/workflow");
1509
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1499
1510
 
1500
1511
  nextStartChildResult = () => ({
1501
1512
  toolResponse: "first",
@@ -1509,7 +1520,12 @@ describe("createSubagentHandler", () => {
1509
1520
  description: "Continue destroy",
1510
1521
  workflow: mockWorkflow(),
1511
1522
  thread: "continue",
1512
- sandbox: { source: "own", continuation: "continue", shutdown: "destroy" },
1523
+ sandbox: {
1524
+ source: "own",
1525
+ continuation: "continue",
1526
+ shutdown: "destroy",
1527
+ proxy: noopSandboxProxy,
1528
+ },
1513
1529
  };
1514
1530
 
1515
1531
  const { handler } = createSubagentHandler([config]);
@@ -1519,11 +1535,314 @@ describe("createSubagentHandler", () => {
1519
1535
  { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1520
1536
  );
1521
1537
 
1522
- const firstCall = startMock.mock.calls.at(-1);
1523
- if (!firstCall) throw new Error("expected startChild call");
1538
+ const firstCall = execMock.mock.calls.at(-1);
1539
+ if (!firstCall) throw new Error("expected executeChild call");
1524
1540
  const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1525
1541
  expect(firstInput.sandboxShutdown).toBe("pause");
1526
1542
  });
1543
+
1544
+ // --- snapshot continuation ---
1545
+
1546
+ it("forces sandboxShutdown=snapshot and passes no sandbox on first call (continuation=snapshot)", async () => {
1547
+ const { executeChild } = await import("@temporalio/workflow");
1548
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1549
+
1550
+ nextStartChildResult = () => ({
1551
+ toolResponse: "first",
1552
+ data: null,
1553
+ threadId: "child-snap-1",
1554
+ baseSnapshot: {
1555
+ sandboxId: "sb-first",
1556
+ providerId: "test",
1557
+ data: { tag: "base" },
1558
+ createdAt: new Date().toISOString(),
1559
+ },
1560
+ snapshot: {
1561
+ sandboxId: "sb-first",
1562
+ providerId: "test",
1563
+ data: { tag: "exit-1" },
1564
+ createdAt: new Date().toISOString(),
1565
+ },
1566
+ });
1567
+
1568
+ const config: SubagentConfig = {
1569
+ agentName: "snap-agent",
1570
+ description: "Snapshot-driven",
1571
+ workflow: mockWorkflow(),
1572
+ thread: "continue",
1573
+ sandbox: {
1574
+ source: "own",
1575
+ init: "once",
1576
+ continuation: "snapshot",
1577
+ proxy: noopSandboxProxy,
1578
+ },
1579
+ };
1580
+
1581
+ const { handler } = createSubagentHandler([config]);
1582
+
1583
+ await handler(
1584
+ { subagent: "snap-agent", description: "test", prompt: "first" },
1585
+ { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1586
+ );
1587
+
1588
+ const firstCall = execMock.mock.calls.at(-1);
1589
+ if (!firstCall) throw new Error("expected executeChild call");
1590
+ const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
1591
+ expect(firstInput.sandbox).toBeUndefined();
1592
+ expect(firstInput.sandboxShutdown).toBe("snapshot");
1593
+ });
1594
+
1595
+ it("boots follow-up from stored thread snapshot on continuation=snapshot", async () => {
1596
+ const { executeChild } = await import("@temporalio/workflow");
1597
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1598
+
1599
+ nextStartChildResult = () => ({
1600
+ toolResponse: "first",
1601
+ data: null,
1602
+ threadId: "child-snap-2",
1603
+ baseSnapshot: {
1604
+ sandboxId: "sb-first",
1605
+ providerId: "test",
1606
+ data: { tag: "base" },
1607
+ createdAt: new Date().toISOString(),
1608
+ },
1609
+ snapshot: {
1610
+ sandboxId: "sb-first",
1611
+ providerId: "test",
1612
+ data: { tag: "exit-1" },
1613
+ createdAt: new Date().toISOString(),
1614
+ },
1615
+ });
1616
+
1617
+ const config: SubagentConfig = {
1618
+ agentName: "snap-agent",
1619
+ description: "Snapshot-driven",
1620
+ workflow: mockWorkflow(),
1621
+ thread: "continue",
1622
+ sandbox: {
1623
+ source: "own",
1624
+ init: "once",
1625
+ continuation: "snapshot",
1626
+ proxy: noopSandboxProxy,
1627
+ },
1628
+ };
1629
+
1630
+ const { handler } = createSubagentHandler([config]);
1631
+
1632
+ await handler(
1633
+ { subagent: "snap-agent", description: "test", prompt: "first" },
1634
+ { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1635
+ );
1636
+
1637
+ nextStartChildResult = () => ({
1638
+ toolResponse: "second",
1639
+ data: null,
1640
+ threadId: "child-snap-2", // same thread — continuation
1641
+ snapshot: {
1642
+ sandboxId: "sb-second",
1643
+ providerId: "test",
1644
+ data: { tag: "exit-2" },
1645
+ createdAt: new Date().toISOString(),
1646
+ },
1647
+ });
1648
+
1649
+ await handler(
1650
+ {
1651
+ subagent: "snap-agent",
1652
+ description: "test",
1653
+ prompt: "second",
1654
+ threadId: "child-snap-2",
1655
+ },
1656
+ { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1657
+ );
1658
+
1659
+ const secondCall = execMock.mock.calls.at(-1);
1660
+ if (!secondCall) throw new Error("expected executeChild call");
1661
+ const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1662
+ expect(secondInput.sandbox).toEqual({
1663
+ mode: "from-snapshot",
1664
+ snapshot: expect.objectContaining({ data: { tag: "exit-1" } }),
1665
+ });
1666
+ expect(secondInput.sandboxShutdown).toBe("snapshot");
1667
+ });
1668
+
1669
+ it("uses per-agent base snapshot for a new thread when init=once + continuation=snapshot", async () => {
1670
+ const { executeChild } = await import("@temporalio/workflow");
1671
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
1672
+
1673
+ // First call — establishes base snapshot.
1674
+ nextStartChildResult = () => ({
1675
+ toolResponse: "first",
1676
+ data: null,
1677
+ threadId: "child-snap-A",
1678
+ baseSnapshot: {
1679
+ sandboxId: "sb-first",
1680
+ providerId: "test",
1681
+ data: { tag: "base" },
1682
+ createdAt: new Date().toISOString(),
1683
+ },
1684
+ snapshot: {
1685
+ sandboxId: "sb-first",
1686
+ providerId: "test",
1687
+ data: { tag: "exit-A" },
1688
+ createdAt: new Date().toISOString(),
1689
+ },
1690
+ });
1691
+
1692
+ const config: SubagentConfig = {
1693
+ agentName: "snap-agent",
1694
+ description: "Snapshot-driven",
1695
+ workflow: mockWorkflow(),
1696
+ thread: "continue",
1697
+ sandbox: {
1698
+ source: "own",
1699
+ init: "once",
1700
+ continuation: "snapshot",
1701
+ proxy: noopSandboxProxy,
1702
+ },
1703
+ };
1704
+
1705
+ const { handler } = createSubagentHandler([config]);
1706
+
1707
+ await handler(
1708
+ { subagent: "snap-agent", description: "test", prompt: "first" },
1709
+ { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1710
+ );
1711
+
1712
+ // Third call: different thread → should use base snapshot.
1713
+ nextStartChildResult = () => ({
1714
+ toolResponse: "new-thread",
1715
+ data: null,
1716
+ threadId: "child-snap-B",
1717
+ snapshot: {
1718
+ sandboxId: "sb-B",
1719
+ providerId: "test",
1720
+ data: { tag: "exit-B" },
1721
+ createdAt: new Date().toISOString(),
1722
+ },
1723
+ });
1724
+
1725
+ await handler(
1726
+ { subagent: "snap-agent", description: "test", prompt: "new" },
1727
+ { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1728
+ );
1729
+
1730
+ const secondCall = execMock.mock.calls.at(-1);
1731
+ if (!secondCall) throw new Error("expected executeChild call");
1732
+ const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
1733
+ expect(secondInput.sandbox).toEqual({
1734
+ mode: "from-snapshot",
1735
+ snapshot: expect.objectContaining({ data: { tag: "base" } }),
1736
+ });
1737
+ });
1738
+
1739
+ it("deletes every stored snapshot via sandbox.proxy during cleanup sweep", async () => {
1740
+ const opsMock = makeMockSandboxOps();
1741
+ const config: SubagentConfig = {
1742
+ agentName: "snap-agent",
1743
+ description: "Snapshot-driven",
1744
+ workflow: mockWorkflow(),
1745
+ thread: "continue",
1746
+ sandbox: {
1747
+ source: "own",
1748
+ init: "once",
1749
+ continuation: "snapshot",
1750
+ proxy: () => opsMock,
1751
+ },
1752
+ };
1753
+
1754
+ const { handler, cleanupSubagentSnapshots } = createSubagentHandler([
1755
+ config,
1756
+ ]);
1757
+
1758
+ // Call 1 — produces base + exit-1.
1759
+ nextStartChildResult = () => ({
1760
+ toolResponse: "first",
1761
+ data: null,
1762
+ threadId: "child-t",
1763
+ baseSnapshot: {
1764
+ sandboxId: "sb-first",
1765
+ providerId: "test",
1766
+ data: { tag: "base" },
1767
+ createdAt: new Date().toISOString(),
1768
+ },
1769
+ snapshot: {
1770
+ sandboxId: "sb-first",
1771
+ providerId: "test",
1772
+ data: { tag: "exit-1" },
1773
+ createdAt: new Date().toISOString(),
1774
+ },
1775
+ });
1776
+ await handler(
1777
+ { subagent: "snap-agent", description: "test", prompt: "first" },
1778
+ { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1779
+ );
1780
+
1781
+ // Call 2 — same thread, produces exit-2 (supersedes exit-1).
1782
+ nextStartChildResult = () => ({
1783
+ toolResponse: "second",
1784
+ data: null,
1785
+ threadId: "child-t",
1786
+ snapshot: {
1787
+ sandboxId: "sb-second",
1788
+ providerId: "test",
1789
+ data: { tag: "exit-2" },
1790
+ createdAt: new Date().toISOString(),
1791
+ },
1792
+ });
1793
+ await handler(
1794
+ {
1795
+ subagent: "snap-agent",
1796
+ description: "test",
1797
+ prompt: "second",
1798
+ threadId: "child-t",
1799
+ },
1800
+ { threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
1801
+ );
1802
+
1803
+ await cleanupSubagentSnapshots();
1804
+
1805
+ // Parent should have deleted both the latest thread snapshot and the
1806
+ // per-agent base snapshot through the subagent's sandbox.proxy.
1807
+ const deletedTags = opsMock.deleteSandboxSnapshot.mock.calls.map(
1808
+ (call) => (call[0] as { data: { tag: string } }).data.tag
1809
+ );
1810
+ expect(deletedTags.sort()).toEqual(["base", "exit-2"]);
1811
+ });
1812
+
1813
+ it("does not call deleteSandboxSnapshot for children that produced no snapshots", async () => {
1814
+ const opsMock = makeMockSandboxOps();
1815
+ const config: SubagentConfig = {
1816
+ agentName: "snap-agent",
1817
+ description: "Snapshot-driven",
1818
+ workflow: mockWorkflow(),
1819
+ thread: "continue",
1820
+ sandbox: {
1821
+ source: "own",
1822
+ init: "once",
1823
+ continuation: "snapshot",
1824
+ proxy: () => opsMock,
1825
+ },
1826
+ };
1827
+
1828
+ const { handler, cleanupSubagentSnapshots } = createSubagentHandler([
1829
+ config,
1830
+ ]);
1831
+
1832
+ nextStartChildResult = () => ({
1833
+ toolResponse: "no-snap",
1834
+ data: null,
1835
+ threadId: "child-t",
1836
+ });
1837
+ await handler(
1838
+ { subagent: "snap-agent", description: "test", prompt: "run" },
1839
+ { threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
1840
+ );
1841
+
1842
+ await cleanupSubagentSnapshots();
1843
+
1844
+ expect(opsMock.deleteSandboxSnapshot).not.toHaveBeenCalled();
1845
+ });
1527
1846
  });
1528
1847
 
1529
1848
  // ---------------------------------------------------------------------------
@@ -1865,64 +2184,23 @@ describe("defineSubagentWorkflow", () => {
1865
2184
  });
1866
2185
  });
1867
2186
 
1868
- it("validates destroySandbox required for keep-until-parent-close", async () => {
1869
- // @ts-expect-error — deliberately omitting destroySandbox to test runtime validation
1870
- const workflow = defineSubagentWorkflow(
1871
- {
1872
- name: "test",
1873
- description: "test agent",
1874
- sandboxShutdown: "keep-until-parent-close",
1875
- },
1876
- async () => ({
1877
- toolResponse: "ok",
1878
- data: null,
1879
- threadId: "t",
1880
- sandboxId: "sb-1",
1881
- })
1882
- );
1883
-
1884
- await expect(workflow("go", {})).rejects.toThrow(
1885
- /keep-until-parent-close.*destroySandbox/
1886
- );
1887
- });
1888
-
1889
- it("validates sandboxId required for keep-until-parent-close", async () => {
1890
- // @ts-expect-error — deliberately omitting sandboxId to test runtime validation
1891
- const workflow = defineSubagentWorkflow(
1892
- {
1893
- name: "test",
1894
- description: "test agent",
1895
- sandboxShutdown: "keep-until-parent-close",
1896
- },
1897
- async () => ({
1898
- toolResponse: "ok",
1899
- data: null,
1900
- threadId: "t",
1901
- destroySandbox: async () => {},
1902
- })
1903
- );
1904
-
1905
- await expect(workflow("go", {})).rejects.toThrow(
1906
- /keep-until-parent-close.*sandboxId/
1907
- );
1908
- });
1909
-
1910
2187
  it("uses keep-until-parent-close from workflowInput override in sessionInput", async () => {
1911
2188
  let capturedSession: SubagentSessionInput | undefined;
1912
2189
  const workflow = defineSubagentWorkflow(
1913
2190
  { name: "test", description: "test agent" },
1914
2191
  async (_prompt, sessionInput) => {
1915
2192
  capturedSession = sessionInput;
1916
- return { toolResponse: "ok", data: null, threadId: "t" };
2193
+ return {
2194
+ toolResponse: "ok",
2195
+ data: null,
2196
+ threadId: "t",
2197
+ sandboxId: "sb-1",
2198
+ };
1917
2199
  }
1918
2200
  );
1919
2201
 
1920
- // Validation will throw because destroySandbox is missing, but sessionInput is captured first
1921
- try {
1922
- await workflow("go", { sandboxShutdown: "keep-until-parent-close" });
1923
- } catch {
1924
- // expected — no destroySandbox callback
1925
- }
2202
+ await workflow("go", { sandboxShutdown: "keep-until-parent-close" });
2203
+
1926
2204
  expect(capturedSession?.sandboxShutdown).toBe("keep-until-parent-close");
1927
2205
  });
1928
2206
  });