zeitlich 0.2.36 → 0.2.37
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.
- package/README.md +146 -92
- package/dist/{activities-BVI2lTwr.d.ts → activities-Bb-nAjwQ.d.ts} +2 -2
- package/dist/{activities-hd4aNnZE.d.cts → activities-vkI4_3CC.d.cts} +2 -2
- package/dist/adapters/sandbox/bedrock/index.cjs +14 -11
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +4 -3
- package/dist/adapters/sandbox/bedrock/index.d.ts +4 -3
- package/dist/adapters/sandbox/bedrock/index.js +14 -11
- package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.cjs +2 -0
- package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.js +2 -0
- package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
- package/dist/adapters/sandbox/daytona/index.cjs +8 -0
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
- package/dist/adapters/sandbox/daytona/index.js +8 -0
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +2 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.js +2 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/e2b/index.cjs +59 -10
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +5 -3
- package/dist/adapters/sandbox/e2b/index.d.ts +5 -3
- package/dist/adapters/sandbox/e2b/index.js +59 -10
- package/dist/adapters/sandbox/e2b/index.js.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.cjs +2 -0
- package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.js +2 -0
- package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +5 -0
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +2 -1
- package/dist/adapters/sandbox/inmemory/index.d.ts +2 -1
- package/dist/adapters/sandbox/inmemory/index.js +5 -0
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +2 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.js +2 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/thread/anthropic/index.cjs +71 -36
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +5 -5
- package/dist/adapters/thread/anthropic/index.d.ts +5 -5
- package/dist/adapters/thread/anthropic/index.js +71 -36
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +5 -1
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
- package/dist/adapters/thread/anthropic/workflow.js +5 -1
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +50 -25
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +5 -5
- package/dist/adapters/thread/google-genai/index.d.ts +5 -5
- package/dist/adapters/thread/google-genai/index.js +50 -25
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +5 -1
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
- package/dist/adapters/thread/google-genai/workflow.js +5 -1
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +34 -7
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +5 -5
- package/dist/adapters/thread/langchain/index.d.ts +5 -5
- package/dist/adapters/thread/langchain/index.js +34 -7
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +5 -1
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
- package/dist/adapters/thread/langchain/workflow.js +5 -1
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +206 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -11
- package/dist/index.d.ts +17 -11
- package/dist/index.js +207 -121
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BjdFGPTm.d.ts → proxy-0smGKvx8.d.ts} +1 -1
- package/dist/{proxy-7RnVaPdJ.d.cts → proxy-DEtowJyd.d.cts} +1 -1
- package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-3fszQih4.d.ts} +2 -2
- package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-C-C4pI2z.d.ts} +2 -2
- package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-CzYln2OC.d.cts} +2 -2
- package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-D4vgzYrh.d.cts} +2 -2
- package/dist/{types-yiXmqedU.d.ts → types-B37hKoWA.d.ts} +1 -1
- package/dist/{types-DQ1l_gXL.d.cts → types-BO7Yju20.d.cts} +63 -14
- package/dist/{types-wiGLvxWf.d.ts → types-CNuWnvy9.d.ts} +1 -1
- package/dist/{types-CADc5V_P.d.ts → types-CPKDl-y_.d.ts} +63 -14
- package/dist/{types-Mc_4BCfT.d.cts → types-D08CXPh8.d.cts} +1 -1
- package/dist/{types-CBH54cwr.d.cts → types-DWEUmYAJ.d.cts} +1 -1
- package/dist/{types-DxCpFNv_.d.cts → types-tQL9njTu.d.cts} +25 -0
- package/dist/{types-DxCpFNv_.d.ts → types-tQL9njTu.d.ts} +25 -0
- package/dist/{workflow-P2pTSfKu.d.ts → workflow-CjXHbZZc.d.ts} +2 -2
- package/dist/{workflow-DhtWRovz.d.cts → workflow-Do_lzJpT.d.cts} +2 -2
- package/dist/workflow.cjs +182 -114
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +3 -3
- package/dist/workflow.d.ts +3 -3
- package/dist/workflow.js +183 -115
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
- package/src/adapters/sandbox/bedrock/index.ts +10 -8
- package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
- package/src/adapters/sandbox/daytona/index.ts +6 -0
- package/src/adapters/sandbox/daytona/proxy.ts +2 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
- package/src/adapters/sandbox/e2b/index.ts +63 -12
- package/src/adapters/sandbox/e2b/proxy.ts +2 -0
- package/src/adapters/sandbox/inmemory/index.ts +5 -0
- package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
- package/src/adapters/thread/anthropic/activities.ts +49 -26
- package/src/adapters/thread/anthropic/model-invoker.ts +15 -6
- package/src/adapters/thread/anthropic/proxy.ts +6 -2
- package/src/adapters/thread/anthropic/thread-manager.test.ts +26 -7
- package/src/adapters/thread/anthropic/thread-manager.ts +60 -46
- package/src/adapters/thread/google-genai/activities.ts +7 -2
- package/src/adapters/thread/google-genai/model-invoker.ts +26 -8
- package/src/adapters/thread/google-genai/proxy.ts +6 -2
- package/src/adapters/thread/google-genai/thread-manager.test.ts +13 -3
- package/src/adapters/thread/google-genai/thread-manager.ts +54 -33
- package/src/adapters/thread/langchain/activities.ts +46 -24
- package/src/adapters/thread/langchain/hooks.test.ts +36 -49
- package/src/adapters/thread/langchain/hooks.ts +18 -5
- package/src/adapters/thread/langchain/model-invoker.ts +3 -3
- package/src/adapters/thread/langchain/proxy.ts +6 -2
- package/src/adapters/thread/langchain/thread-manager.test.ts +5 -1
- package/src/adapters/thread/langchain/thread-manager.ts +20 -9
- package/src/index.ts +4 -1
- package/src/lib/activity.ts +16 -6
- package/src/lib/hooks/types.ts +6 -6
- package/src/lib/lifecycle.ts +9 -1
- package/src/lib/model/proxy.ts +2 -2
- package/src/lib/observability/hooks.ts +4 -5
- package/src/lib/observability/index.ts +1 -4
- package/src/lib/sandbox/manager.ts +21 -4
- package/src/lib/sandbox/node-fs.ts +3 -6
- package/src/lib/sandbox/sandbox.test.ts +36 -3
- package/src/lib/sandbox/tree.integration.test.ts +10 -3
- package/src/lib/sandbox/types.ts +35 -1
- package/src/lib/session/session-edge-cases.integration.test.ts +51 -13
- package/src/lib/session/session.integration.test.ts +139 -0
- package/src/lib/session/session.ts +50 -19
- package/src/lib/session/types.ts +13 -5
- package/src/lib/skills/fs-provider.ts +12 -8
- package/src/lib/skills/handler.ts +1 -1
- package/src/lib/skills/parse.ts +3 -1
- package/src/lib/skills/register.ts +1 -3
- package/src/lib/skills/skills.integration.test.ts +25 -15
- package/src/lib/state/manager.integration.test.ts +12 -2
- package/src/lib/subagent/define.ts +1 -1
- package/src/lib/subagent/handler.ts +186 -71
- package/src/lib/subagent/index.ts +1 -5
- package/src/lib/subagent/register.ts +3 -2
- package/src/lib/subagent/signals.ts +1 -10
- package/src/lib/subagent/subagent.integration.test.ts +438 -156
- package/src/lib/subagent/tool.ts +4 -3
- package/src/lib/subagent/types.ts +50 -20
- package/src/lib/subagent/workflow.ts +9 -49
- package/src/lib/thread/id.test.ts +1 -1
- package/src/lib/thread/id.ts +1 -2
- package/src/lib/thread/proxy.ts +3 -4
- package/src/lib/thread/types.ts +11 -3
- package/src/lib/tool-router/index.ts +1 -5
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +1 -1
- package/src/lib/tool-router/router.ts +3 -2
- package/src/lib/tool-router/types.ts +11 -3
- package/src/lib/tool-router/with-sandbox.ts +19 -5
- package/src/lib/virtual-fs/filesystem.ts +1 -1
- package/src/lib/virtual-fs/index.ts +5 -1
- package/src/lib/virtual-fs/mutations.ts +2 -4
- package/src/lib/virtual-fs/queries.ts +9 -5
- package/src/lib/virtual-fs/types.ts +4 -1
- package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
- package/src/lib/workflow.test.ts +7 -4
- package/src/lib/workflow.ts +1 -5
- package/src/tools/ask-user-question/tool.ts +1 -3
- package/src/tools/glob/handler.ts +1 -4
- package/src/tools/task-get/handler.ts +4 -5
- package/src/tools/task-list/handler.ts +1 -4
- package/src/tools/task-update/handler.ts +4 -5
- package/src/workflow.ts +20 -7
- package/tsup.config.ts +9 -6
- package/src/lib/.env +0 -1
- 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,11 +32,7 @@ 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
|
}),
|
|
@@ -57,12 +51,6 @@ 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
54
|
return {
|
|
67
55
|
signal: vi.fn(),
|
|
68
56
|
result: () => Promise.resolve(result),
|
|
@@ -104,7 +92,6 @@ import type {
|
|
|
104
92
|
} from "./types";
|
|
105
93
|
afterEach(() => {
|
|
106
94
|
nextStartChildResult = null;
|
|
107
|
-
capturedSignalHandlers.clear();
|
|
108
95
|
});
|
|
109
96
|
|
|
110
97
|
function mockWorkflow(name?: string): SubagentWorkflow {
|
|
@@ -117,6 +104,27 @@ function mockWorkflow(name?: string): SubagentWorkflow {
|
|
|
117
104
|
return fn as SubagentWorkflow;
|
|
118
105
|
}
|
|
119
106
|
|
|
107
|
+
function makeMockSandboxOps() {
|
|
108
|
+
return {
|
|
109
|
+
createSandbox: vi.fn(),
|
|
110
|
+
destroySandbox: vi.fn(),
|
|
111
|
+
pauseSandbox: vi.fn(),
|
|
112
|
+
resumeSandbox: vi.fn(),
|
|
113
|
+
snapshotSandbox: vi.fn(),
|
|
114
|
+
restoreSandbox: vi.fn(),
|
|
115
|
+
deleteSandboxSnapshot: vi.fn(),
|
|
116
|
+
forkSandbox: vi.fn(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Default no-op sandbox proxy factory — satisfies the runtime check that
|
|
122
|
+
* every sandbox-using subagent must declare `sandbox.proxy`. Tests that
|
|
123
|
+
* need to assert on destroy/cleanup create their own mock ops and wire
|
|
124
|
+
* them through `sandbox: { ..., proxy: () => opsMock }`.
|
|
125
|
+
*/
|
|
126
|
+
const noopSandboxProxy = () => makeMockSandboxOps();
|
|
127
|
+
|
|
120
128
|
// ---------------------------------------------------------------------------
|
|
121
129
|
// createSubagentTool
|
|
122
130
|
// ---------------------------------------------------------------------------
|
|
@@ -377,7 +385,11 @@ describe("createSubagentHandler", () => {
|
|
|
377
385
|
agentName: "inherit-agent",
|
|
378
386
|
description: "Inherits sandbox",
|
|
379
387
|
workflow: mockWorkflow(),
|
|
380
|
-
sandbox: {
|
|
388
|
+
sandbox: {
|
|
389
|
+
source: "inherit",
|
|
390
|
+
continuation: "continue",
|
|
391
|
+
proxy: noopSandboxProxy,
|
|
392
|
+
},
|
|
381
393
|
};
|
|
382
394
|
|
|
383
395
|
const { handler } = createSubagentHandler([inheritSubagent]);
|
|
@@ -406,7 +418,11 @@ describe("createSubagentHandler", () => {
|
|
|
406
418
|
agentName: "inherit-agent",
|
|
407
419
|
description: "Inherits sandbox",
|
|
408
420
|
workflow: mockWorkflow(),
|
|
409
|
-
sandbox: {
|
|
421
|
+
sandbox: {
|
|
422
|
+
source: "inherit",
|
|
423
|
+
continuation: "continue",
|
|
424
|
+
proxy: noopSandboxProxy,
|
|
425
|
+
},
|
|
410
426
|
};
|
|
411
427
|
|
|
412
428
|
const { handler } = createSubagentHandler([inheritSubagent]);
|
|
@@ -427,7 +443,7 @@ describe("createSubagentHandler", () => {
|
|
|
427
443
|
agentName: "own-agent",
|
|
428
444
|
description: "Own sandbox",
|
|
429
445
|
workflow: mockWorkflow(),
|
|
430
|
-
sandbox: { source: "own", continuation: "fork" },
|
|
446
|
+
sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
|
|
431
447
|
};
|
|
432
448
|
|
|
433
449
|
const { handler } = createSubagentHandler([ownSubagent]);
|
|
@@ -508,7 +524,7 @@ describe("createSubagentHandler", () => {
|
|
|
508
524
|
agentName: "own-agent",
|
|
509
525
|
description: "Own sandbox",
|
|
510
526
|
workflow: mockWorkflow(),
|
|
511
|
-
sandbox: { source: "own", continuation: "fork" },
|
|
527
|
+
sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
|
|
512
528
|
};
|
|
513
529
|
|
|
514
530
|
const { handler } = createSubagentHandler([ownSubagent]);
|
|
@@ -670,7 +686,7 @@ describe("createSubagentHandler", () => {
|
|
|
670
686
|
description: "Sandbox continuation",
|
|
671
687
|
workflow: mockWorkflow(),
|
|
672
688
|
thread: "fork",
|
|
673
|
-
sandbox: { source: "own", continuation: "fork" },
|
|
689
|
+
sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
|
|
674
690
|
};
|
|
675
691
|
|
|
676
692
|
const { handler } = createSubagentHandler([contSandboxSubagent]);
|
|
@@ -726,7 +742,7 @@ describe("createSubagentHandler", () => {
|
|
|
726
742
|
description: "Sandbox continuation",
|
|
727
743
|
workflow: mockWorkflow(),
|
|
728
744
|
thread: "fork",
|
|
729
|
-
sandbox: { source: "own", continuation: "fork" },
|
|
745
|
+
sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
|
|
730
746
|
};
|
|
731
747
|
|
|
732
748
|
const { handler } = createSubagentHandler([contSandboxSubagent]);
|
|
@@ -755,10 +771,7 @@ describe("createSubagentHandler", () => {
|
|
|
755
771
|
expect(workflowInput.thread).toBeUndefined();
|
|
756
772
|
});
|
|
757
773
|
|
|
758
|
-
it("does not
|
|
759
|
-
const { startChild } = await import("@temporalio/workflow");
|
|
760
|
-
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
761
|
-
|
|
774
|
+
it("does not destroy fork-mode subagent sandbox without pause-until-parent-close", async () => {
|
|
762
775
|
const contSandboxSubagent: SubagentConfig = {
|
|
763
776
|
agentName: "sb-cont",
|
|
764
777
|
description: "Sandbox continuation",
|
|
@@ -775,25 +788,16 @@ describe("createSubagentHandler", () => {
|
|
|
775
788
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
776
789
|
);
|
|
777
790
|
|
|
778
|
-
|
|
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();
|
|
791
|
+
await expect(destroySubagentSandboxes()).resolves.toBeUndefined();
|
|
786
792
|
});
|
|
787
793
|
|
|
788
|
-
it("does not
|
|
789
|
-
const
|
|
790
|
-
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
791
|
-
|
|
794
|
+
it("does not destroy sandbox=own with default shutdown", async () => {
|
|
795
|
+
const opsMock = makeMockSandboxOps();
|
|
792
796
|
const ownSubagent: SubagentConfig = {
|
|
793
797
|
agentName: "own-agent",
|
|
794
798
|
description: "Own sandbox",
|
|
795
799
|
workflow: mockWorkflow(),
|
|
796
|
-
sandbox: { source: "own", continuation: "fork" },
|
|
800
|
+
sandbox: { source: "own", continuation: "fork", proxy: () => opsMock },
|
|
797
801
|
};
|
|
798
802
|
|
|
799
803
|
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
@@ -805,20 +809,20 @@ describe("createSubagentHandler", () => {
|
|
|
805
809
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
806
810
|
);
|
|
807
811
|
|
|
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
812
|
await destroySubagentSandboxes();
|
|
814
813
|
|
|
815
|
-
expect(
|
|
814
|
+
expect(opsMock.destroySandbox).not.toHaveBeenCalled();
|
|
816
815
|
});
|
|
817
816
|
|
|
818
|
-
it("
|
|
819
|
-
|
|
820
|
-
|
|
817
|
+
it("destroys sandbox via sandbox.proxy for own + pause-until-parent-close", async () => {
|
|
818
|
+
nextStartChildResult = () => ({
|
|
819
|
+
toolResponse: "done",
|
|
820
|
+
data: null,
|
|
821
|
+
threadId: "child-t",
|
|
822
|
+
sandboxId: "child-sb-99",
|
|
823
|
+
});
|
|
821
824
|
|
|
825
|
+
const opsMock = makeMockSandboxOps();
|
|
822
826
|
const ownSubagent: SubagentConfig = {
|
|
823
827
|
agentName: "own-agent",
|
|
824
828
|
description: "Own sandbox",
|
|
@@ -827,6 +831,7 @@ describe("createSubagentHandler", () => {
|
|
|
827
831
|
source: "own",
|
|
828
832
|
continuation: "fork",
|
|
829
833
|
shutdown: "pause-until-parent-close",
|
|
834
|
+
proxy: () => opsMock,
|
|
830
835
|
},
|
|
831
836
|
};
|
|
832
837
|
|
|
@@ -841,21 +846,20 @@ describe("createSubagentHandler", () => {
|
|
|
841
846
|
|
|
842
847
|
await destroySubagentSandboxes();
|
|
843
848
|
|
|
844
|
-
|
|
845
|
-
if (!lastResult) throw new Error("expected startChild call");
|
|
846
|
-
const childHandle = await lastResult.value;
|
|
847
|
-
expect(childHandle.signal).toHaveBeenCalled();
|
|
849
|
+
expect(opsMock.destroySandbox).toHaveBeenCalledWith("child-sb-99");
|
|
848
850
|
});
|
|
849
851
|
|
|
850
|
-
it("does not
|
|
851
|
-
const
|
|
852
|
-
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
853
|
-
|
|
852
|
+
it("does not destroy inherit subagents' sandbox", async () => {
|
|
853
|
+
const opsMock = makeMockSandboxOps();
|
|
854
854
|
const inheritSubagent: SubagentConfig = {
|
|
855
855
|
agentName: "inherit-agent",
|
|
856
856
|
description: "Inherits sandbox",
|
|
857
857
|
workflow: mockWorkflow(),
|
|
858
|
-
sandbox: {
|
|
858
|
+
sandbox: {
|
|
859
|
+
source: "inherit",
|
|
860
|
+
continuation: "continue",
|
|
861
|
+
proxy: () => opsMock,
|
|
862
|
+
},
|
|
859
863
|
};
|
|
860
864
|
|
|
861
865
|
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
@@ -872,14 +876,9 @@ describe("createSubagentHandler", () => {
|
|
|
872
876
|
}
|
|
873
877
|
);
|
|
874
878
|
|
|
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
879
|
await destroySubagentSandboxes();
|
|
881
880
|
|
|
882
|
-
expect(
|
|
881
|
+
expect(opsMock.destroySandbox).not.toHaveBeenCalled();
|
|
883
882
|
});
|
|
884
883
|
|
|
885
884
|
it("does not pass sandboxId when sandbox is none (default)", async () => {
|
|
@@ -939,10 +938,7 @@ describe("createSubagentHandler", () => {
|
|
|
939
938
|
expect(workflowInput.sandbox).toBeUndefined();
|
|
940
939
|
});
|
|
941
940
|
|
|
942
|
-
it("
|
|
943
|
-
const { startChild } = await import("@temporalio/workflow");
|
|
944
|
-
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
945
|
-
|
|
941
|
+
it("destroySubagentSandboxes is a no-op for none subagents", async () => {
|
|
946
942
|
const noneSubagent: SubagentConfig = {
|
|
947
943
|
agentName: "none-agent",
|
|
948
944
|
description: "No sandbox",
|
|
@@ -959,14 +955,7 @@ describe("createSubagentHandler", () => {
|
|
|
959
955
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
960
956
|
);
|
|
961
957
|
|
|
962
|
-
|
|
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();
|
|
958
|
+
await expect(destroySubagentSandboxes()).resolves.toBeUndefined();
|
|
970
959
|
});
|
|
971
960
|
|
|
972
961
|
// --- inherit + continuation: fork ---
|
|
@@ -979,7 +968,11 @@ describe("createSubagentHandler", () => {
|
|
|
979
968
|
agentName: "inherit-fork",
|
|
980
969
|
description: "Inherit fork",
|
|
981
970
|
workflow: mockWorkflow(),
|
|
982
|
-
sandbox: {
|
|
971
|
+
sandbox: {
|
|
972
|
+
source: "inherit",
|
|
973
|
+
continuation: "fork",
|
|
974
|
+
proxy: noopSandboxProxy,
|
|
975
|
+
},
|
|
983
976
|
};
|
|
984
977
|
|
|
985
978
|
const { handler } = createSubagentHandler([config]);
|
|
@@ -1021,7 +1014,11 @@ describe("createSubagentHandler", () => {
|
|
|
1021
1014
|
description: "Own continue",
|
|
1022
1015
|
workflow: mockWorkflow(),
|
|
1023
1016
|
thread: "continue",
|
|
1024
|
-
sandbox: {
|
|
1017
|
+
sandbox: {
|
|
1018
|
+
source: "own",
|
|
1019
|
+
continuation: "continue",
|
|
1020
|
+
proxy: noopSandboxProxy,
|
|
1021
|
+
},
|
|
1025
1022
|
};
|
|
1026
1023
|
|
|
1027
1024
|
const { handler } = createSubagentHandler([config]);
|
|
@@ -1075,7 +1072,12 @@ describe("createSubagentHandler", () => {
|
|
|
1075
1072
|
agentName: "lazy-fork",
|
|
1076
1073
|
description: "Lazy fork",
|
|
1077
1074
|
workflow: mockWorkflow(),
|
|
1078
|
-
sandbox: {
|
|
1075
|
+
sandbox: {
|
|
1076
|
+
source: "own",
|
|
1077
|
+
init: "once",
|
|
1078
|
+
continuation: "fork",
|
|
1079
|
+
proxy: noopSandboxProxy,
|
|
1080
|
+
},
|
|
1079
1081
|
};
|
|
1080
1082
|
|
|
1081
1083
|
const { handler } = createSubagentHandler([config]);
|
|
@@ -1131,7 +1133,12 @@ describe("createSubagentHandler", () => {
|
|
|
1131
1133
|
agentName: "lazy-cont",
|
|
1132
1134
|
description: "Lazy continue",
|
|
1133
1135
|
workflow: mockWorkflow(),
|
|
1134
|
-
sandbox: {
|
|
1136
|
+
sandbox: {
|
|
1137
|
+
source: "own",
|
|
1138
|
+
init: "once",
|
|
1139
|
+
continuation: "continue",
|
|
1140
|
+
proxy: noopSandboxProxy,
|
|
1141
|
+
},
|
|
1135
1142
|
};
|
|
1136
1143
|
|
|
1137
1144
|
const { handler } = createSubagentHandler([config]);
|
|
@@ -1165,10 +1172,7 @@ describe("createSubagentHandler", () => {
|
|
|
1165
1172
|
|
|
1166
1173
|
// --- init: once cleanup ---
|
|
1167
1174
|
|
|
1168
|
-
it("
|
|
1169
|
-
const { startChild } = await import("@temporalio/workflow");
|
|
1170
|
-
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
1171
|
-
|
|
1175
|
+
it("destroys the persistent sandbox for init=once at parent shutdown", async () => {
|
|
1172
1176
|
nextStartChildResult = () => ({
|
|
1173
1177
|
toolResponse: "done",
|
|
1174
1178
|
data: null,
|
|
@@ -1176,11 +1180,17 @@ describe("createSubagentHandler", () => {
|
|
|
1176
1180
|
sandboxId: "persistent-sb",
|
|
1177
1181
|
});
|
|
1178
1182
|
|
|
1183
|
+
const opsMock = makeMockSandboxOps();
|
|
1179
1184
|
const config: SubagentConfig = {
|
|
1180
1185
|
agentName: "lazy-cleanup",
|
|
1181
1186
|
description: "Lazy cleanup",
|
|
1182
1187
|
workflow: mockWorkflow(),
|
|
1183
|
-
sandbox: {
|
|
1188
|
+
sandbox: {
|
|
1189
|
+
source: "own",
|
|
1190
|
+
init: "once",
|
|
1191
|
+
continuation: "fork",
|
|
1192
|
+
proxy: () => opsMock,
|
|
1193
|
+
},
|
|
1184
1194
|
};
|
|
1185
1195
|
|
|
1186
1196
|
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
@@ -1194,10 +1204,7 @@ describe("createSubagentHandler", () => {
|
|
|
1194
1204
|
|
|
1195
1205
|
await destroySubagentSandboxes();
|
|
1196
1206
|
|
|
1197
|
-
|
|
1198
|
-
if (!lastResult) throw new Error("expected startChild call");
|
|
1199
|
-
const childHandle = await lastResult.value;
|
|
1200
|
-
expect(childHandle.signal).toHaveBeenCalled();
|
|
1207
|
+
expect(opsMock.destroySandbox).toHaveBeenCalledWith("persistent-sb");
|
|
1201
1208
|
});
|
|
1202
1209
|
|
|
1203
1210
|
it("returns sandboxId in response when child creates a sandbox", async () => {
|
|
@@ -1212,7 +1219,7 @@ describe("createSubagentHandler", () => {
|
|
|
1212
1219
|
agentName: "own-agent",
|
|
1213
1220
|
description: "Own sandbox",
|
|
1214
1221
|
workflow: mockWorkflow(),
|
|
1215
|
-
sandbox: { source: "own", continuation: "fork" },
|
|
1222
|
+
sandbox: { source: "own", continuation: "fork", proxy: noopSandboxProxy },
|
|
1216
1223
|
};
|
|
1217
1224
|
|
|
1218
1225
|
const { handler } = createSubagentHandler([ownSubagent]);
|
|
@@ -1325,10 +1332,15 @@ describe("createSubagentHandler", () => {
|
|
|
1325
1332
|
|
|
1326
1333
|
// --- keep-until-parent-close ---
|
|
1327
1334
|
|
|
1328
|
-
it("
|
|
1329
|
-
|
|
1330
|
-
|
|
1335
|
+
it("destroys sandbox for own + keep-until-parent-close", async () => {
|
|
1336
|
+
nextStartChildResult = () => ({
|
|
1337
|
+
toolResponse: "done",
|
|
1338
|
+
data: null,
|
|
1339
|
+
threadId: "child-t",
|
|
1340
|
+
sandboxId: "child-sb-77",
|
|
1341
|
+
});
|
|
1331
1342
|
|
|
1343
|
+
const opsMock = makeMockSandboxOps();
|
|
1332
1344
|
const ownSubagent: SubagentConfig = {
|
|
1333
1345
|
agentName: "own-keep",
|
|
1334
1346
|
description: "Own sandbox kept",
|
|
@@ -1337,6 +1349,7 @@ describe("createSubagentHandler", () => {
|
|
|
1337
1349
|
source: "own",
|
|
1338
1350
|
continuation: "fork",
|
|
1339
1351
|
shutdown: "keep-until-parent-close",
|
|
1352
|
+
proxy: () => opsMock,
|
|
1340
1353
|
},
|
|
1341
1354
|
};
|
|
1342
1355
|
|
|
@@ -1351,21 +1364,21 @@ describe("createSubagentHandler", () => {
|
|
|
1351
1364
|
|
|
1352
1365
|
await destroySubagentSandboxes();
|
|
1353
1366
|
|
|
1354
|
-
|
|
1355
|
-
if (!lastResult) throw new Error("expected startChild call");
|
|
1356
|
-
const childHandle = await lastResult.value;
|
|
1357
|
-
expect(childHandle.signal).toHaveBeenCalled();
|
|
1367
|
+
expect(opsMock.destroySandbox).toHaveBeenCalledWith("child-sb-77");
|
|
1358
1368
|
});
|
|
1359
1369
|
|
|
1360
|
-
it("does not
|
|
1361
|
-
const
|
|
1362
|
-
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
1363
|
-
|
|
1370
|
+
it("does not destroy sandbox for own + keep (without parent-close)", async () => {
|
|
1371
|
+
const opsMock = makeMockSandboxOps();
|
|
1364
1372
|
const ownSubagent: SubagentConfig = {
|
|
1365
1373
|
agentName: "own-keep-plain",
|
|
1366
1374
|
description: "Own sandbox keep",
|
|
1367
1375
|
workflow: mockWorkflow(),
|
|
1368
|
-
sandbox: {
|
|
1376
|
+
sandbox: {
|
|
1377
|
+
source: "own",
|
|
1378
|
+
continuation: "fork",
|
|
1379
|
+
shutdown: "keep",
|
|
1380
|
+
proxy: () => opsMock,
|
|
1381
|
+
},
|
|
1369
1382
|
};
|
|
1370
1383
|
|
|
1371
1384
|
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
@@ -1377,14 +1390,9 @@ describe("createSubagentHandler", () => {
|
|
|
1377
1390
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
1378
1391
|
);
|
|
1379
1392
|
|
|
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
1393
|
await destroySubagentSandboxes();
|
|
1386
1394
|
|
|
1387
|
-
expect(
|
|
1395
|
+
expect(opsMock.destroySandbox).not.toHaveBeenCalled();
|
|
1388
1396
|
});
|
|
1389
1397
|
|
|
1390
1398
|
// --- mustSurvive does not override user shutdown ---
|
|
@@ -1409,6 +1417,7 @@ describe("createSubagentHandler", () => {
|
|
|
1409
1417
|
init: "once",
|
|
1410
1418
|
continuation: "fork",
|
|
1411
1419
|
shutdown: "keep-until-parent-close",
|
|
1420
|
+
proxy: noopSandboxProxy,
|
|
1412
1421
|
},
|
|
1413
1422
|
};
|
|
1414
1423
|
|
|
@@ -1445,6 +1454,7 @@ describe("createSubagentHandler", () => {
|
|
|
1445
1454
|
init: "once",
|
|
1446
1455
|
continuation: "fork",
|
|
1447
1456
|
shutdown: "pause",
|
|
1457
|
+
proxy: noopSandboxProxy,
|
|
1448
1458
|
},
|
|
1449
1459
|
};
|
|
1450
1460
|
|
|
@@ -1477,7 +1487,12 @@ describe("createSubagentHandler", () => {
|
|
|
1477
1487
|
description: "Continue keep",
|
|
1478
1488
|
workflow: mockWorkflow(),
|
|
1479
1489
|
thread: "continue",
|
|
1480
|
-
sandbox: {
|
|
1490
|
+
sandbox: {
|
|
1491
|
+
source: "own",
|
|
1492
|
+
continuation: "continue",
|
|
1493
|
+
shutdown: "keep",
|
|
1494
|
+
proxy: noopSandboxProxy,
|
|
1495
|
+
},
|
|
1481
1496
|
};
|
|
1482
1497
|
|
|
1483
1498
|
const { handler } = createSubagentHandler([config]);
|
|
@@ -1509,7 +1524,12 @@ describe("createSubagentHandler", () => {
|
|
|
1509
1524
|
description: "Continue destroy",
|
|
1510
1525
|
workflow: mockWorkflow(),
|
|
1511
1526
|
thread: "continue",
|
|
1512
|
-
sandbox: {
|
|
1527
|
+
sandbox: {
|
|
1528
|
+
source: "own",
|
|
1529
|
+
continuation: "continue",
|
|
1530
|
+
shutdown: "destroy",
|
|
1531
|
+
proxy: noopSandboxProxy,
|
|
1532
|
+
},
|
|
1513
1533
|
};
|
|
1514
1534
|
|
|
1515
1535
|
const { handler } = createSubagentHandler([config]);
|
|
@@ -1524,6 +1544,309 @@ describe("createSubagentHandler", () => {
|
|
|
1524
1544
|
const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
|
|
1525
1545
|
expect(firstInput.sandboxShutdown).toBe("pause");
|
|
1526
1546
|
});
|
|
1547
|
+
|
|
1548
|
+
// --- snapshot continuation ---
|
|
1549
|
+
|
|
1550
|
+
it("forces sandboxShutdown=snapshot and passes no sandbox on first call (continuation=snapshot)", async () => {
|
|
1551
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
1552
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
1553
|
+
|
|
1554
|
+
nextStartChildResult = () => ({
|
|
1555
|
+
toolResponse: "first",
|
|
1556
|
+
data: null,
|
|
1557
|
+
threadId: "child-snap-1",
|
|
1558
|
+
baseSnapshot: {
|
|
1559
|
+
sandboxId: "sb-first",
|
|
1560
|
+
providerId: "test",
|
|
1561
|
+
data: { tag: "base" },
|
|
1562
|
+
createdAt: new Date().toISOString(),
|
|
1563
|
+
},
|
|
1564
|
+
snapshot: {
|
|
1565
|
+
sandboxId: "sb-first",
|
|
1566
|
+
providerId: "test",
|
|
1567
|
+
data: { tag: "exit-1" },
|
|
1568
|
+
createdAt: new Date().toISOString(),
|
|
1569
|
+
},
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
const config: SubagentConfig = {
|
|
1573
|
+
agentName: "snap-agent",
|
|
1574
|
+
description: "Snapshot-driven",
|
|
1575
|
+
workflow: mockWorkflow(),
|
|
1576
|
+
thread: "continue",
|
|
1577
|
+
sandbox: {
|
|
1578
|
+
source: "own",
|
|
1579
|
+
init: "once",
|
|
1580
|
+
continuation: "snapshot",
|
|
1581
|
+
proxy: noopSandboxProxy,
|
|
1582
|
+
},
|
|
1583
|
+
};
|
|
1584
|
+
|
|
1585
|
+
const { handler } = createSubagentHandler([config]);
|
|
1586
|
+
|
|
1587
|
+
await handler(
|
|
1588
|
+
{ subagent: "snap-agent", description: "test", prompt: "first" },
|
|
1589
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1590
|
+
);
|
|
1591
|
+
|
|
1592
|
+
const firstCall = startMock.mock.calls.at(-1);
|
|
1593
|
+
if (!firstCall) throw new Error("expected startChild call");
|
|
1594
|
+
const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
|
|
1595
|
+
expect(firstInput.sandbox).toBeUndefined();
|
|
1596
|
+
expect(firstInput.sandboxShutdown).toBe("snapshot");
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
it("boots follow-up from stored thread snapshot on continuation=snapshot", async () => {
|
|
1600
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
1601
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
1602
|
+
|
|
1603
|
+
nextStartChildResult = () => ({
|
|
1604
|
+
toolResponse: "first",
|
|
1605
|
+
data: null,
|
|
1606
|
+
threadId: "child-snap-2",
|
|
1607
|
+
baseSnapshot: {
|
|
1608
|
+
sandboxId: "sb-first",
|
|
1609
|
+
providerId: "test",
|
|
1610
|
+
data: { tag: "base" },
|
|
1611
|
+
createdAt: new Date().toISOString(),
|
|
1612
|
+
},
|
|
1613
|
+
snapshot: {
|
|
1614
|
+
sandboxId: "sb-first",
|
|
1615
|
+
providerId: "test",
|
|
1616
|
+
data: { tag: "exit-1" },
|
|
1617
|
+
createdAt: new Date().toISOString(),
|
|
1618
|
+
},
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
const config: SubagentConfig = {
|
|
1622
|
+
agentName: "snap-agent",
|
|
1623
|
+
description: "Snapshot-driven",
|
|
1624
|
+
workflow: mockWorkflow(),
|
|
1625
|
+
thread: "continue",
|
|
1626
|
+
sandbox: {
|
|
1627
|
+
source: "own",
|
|
1628
|
+
init: "once",
|
|
1629
|
+
continuation: "snapshot",
|
|
1630
|
+
proxy: noopSandboxProxy,
|
|
1631
|
+
},
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
const { handler } = createSubagentHandler([config]);
|
|
1635
|
+
|
|
1636
|
+
await handler(
|
|
1637
|
+
{ subagent: "snap-agent", description: "test", prompt: "first" },
|
|
1638
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1639
|
+
);
|
|
1640
|
+
|
|
1641
|
+
nextStartChildResult = () => ({
|
|
1642
|
+
toolResponse: "second",
|
|
1643
|
+
data: null,
|
|
1644
|
+
threadId: "child-snap-2", // same thread — continuation
|
|
1645
|
+
snapshot: {
|
|
1646
|
+
sandboxId: "sb-second",
|
|
1647
|
+
providerId: "test",
|
|
1648
|
+
data: { tag: "exit-2" },
|
|
1649
|
+
createdAt: new Date().toISOString(),
|
|
1650
|
+
},
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
await handler(
|
|
1654
|
+
{
|
|
1655
|
+
subagent: "snap-agent",
|
|
1656
|
+
description: "test",
|
|
1657
|
+
prompt: "second",
|
|
1658
|
+
threadId: "child-snap-2",
|
|
1659
|
+
},
|
|
1660
|
+
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1661
|
+
);
|
|
1662
|
+
|
|
1663
|
+
const secondCall = startMock.mock.calls.at(-1);
|
|
1664
|
+
if (!secondCall) throw new Error("expected startChild call");
|
|
1665
|
+
const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1666
|
+
expect(secondInput.sandbox).toEqual({
|
|
1667
|
+
mode: "from-snapshot",
|
|
1668
|
+
snapshot: expect.objectContaining({ data: { tag: "exit-1" } }),
|
|
1669
|
+
});
|
|
1670
|
+
expect(secondInput.sandboxShutdown).toBe("snapshot");
|
|
1671
|
+
});
|
|
1672
|
+
|
|
1673
|
+
it("uses per-agent base snapshot for a new thread when init=once + continuation=snapshot", async () => {
|
|
1674
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
1675
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
1676
|
+
|
|
1677
|
+
// First call — establishes base snapshot.
|
|
1678
|
+
nextStartChildResult = () => ({
|
|
1679
|
+
toolResponse: "first",
|
|
1680
|
+
data: null,
|
|
1681
|
+
threadId: "child-snap-A",
|
|
1682
|
+
baseSnapshot: {
|
|
1683
|
+
sandboxId: "sb-first",
|
|
1684
|
+
providerId: "test",
|
|
1685
|
+
data: { tag: "base" },
|
|
1686
|
+
createdAt: new Date().toISOString(),
|
|
1687
|
+
},
|
|
1688
|
+
snapshot: {
|
|
1689
|
+
sandboxId: "sb-first",
|
|
1690
|
+
providerId: "test",
|
|
1691
|
+
data: { tag: "exit-A" },
|
|
1692
|
+
createdAt: new Date().toISOString(),
|
|
1693
|
+
},
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
const config: SubagentConfig = {
|
|
1697
|
+
agentName: "snap-agent",
|
|
1698
|
+
description: "Snapshot-driven",
|
|
1699
|
+
workflow: mockWorkflow(),
|
|
1700
|
+
thread: "continue",
|
|
1701
|
+
sandbox: {
|
|
1702
|
+
source: "own",
|
|
1703
|
+
init: "once",
|
|
1704
|
+
continuation: "snapshot",
|
|
1705
|
+
proxy: noopSandboxProxy,
|
|
1706
|
+
},
|
|
1707
|
+
};
|
|
1708
|
+
|
|
1709
|
+
const { handler } = createSubagentHandler([config]);
|
|
1710
|
+
|
|
1711
|
+
await handler(
|
|
1712
|
+
{ subagent: "snap-agent", description: "test", prompt: "first" },
|
|
1713
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1714
|
+
);
|
|
1715
|
+
|
|
1716
|
+
// Third call: different thread → should use base snapshot.
|
|
1717
|
+
nextStartChildResult = () => ({
|
|
1718
|
+
toolResponse: "new-thread",
|
|
1719
|
+
data: null,
|
|
1720
|
+
threadId: "child-snap-B",
|
|
1721
|
+
snapshot: {
|
|
1722
|
+
sandboxId: "sb-B",
|
|
1723
|
+
providerId: "test",
|
|
1724
|
+
data: { tag: "exit-B" },
|
|
1725
|
+
createdAt: new Date().toISOString(),
|
|
1726
|
+
},
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
await handler(
|
|
1730
|
+
{ subagent: "snap-agent", description: "test", prompt: "new" },
|
|
1731
|
+
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1732
|
+
);
|
|
1733
|
+
|
|
1734
|
+
const secondCall = startMock.mock.calls.at(-1);
|
|
1735
|
+
if (!secondCall) throw new Error("expected startChild call");
|
|
1736
|
+
const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1737
|
+
expect(secondInput.sandbox).toEqual({
|
|
1738
|
+
mode: "from-snapshot",
|
|
1739
|
+
snapshot: expect.objectContaining({ data: { tag: "base" } }),
|
|
1740
|
+
});
|
|
1741
|
+
});
|
|
1742
|
+
|
|
1743
|
+
it("deletes every stored snapshot via sandbox.proxy during cleanup sweep", async () => {
|
|
1744
|
+
const opsMock = makeMockSandboxOps();
|
|
1745
|
+
const config: SubagentConfig = {
|
|
1746
|
+
agentName: "snap-agent",
|
|
1747
|
+
description: "Snapshot-driven",
|
|
1748
|
+
workflow: mockWorkflow(),
|
|
1749
|
+
thread: "continue",
|
|
1750
|
+
sandbox: {
|
|
1751
|
+
source: "own",
|
|
1752
|
+
init: "once",
|
|
1753
|
+
continuation: "snapshot",
|
|
1754
|
+
proxy: () => opsMock,
|
|
1755
|
+
},
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1758
|
+
const { handler, cleanupSubagentSnapshots } = createSubagentHandler([
|
|
1759
|
+
config,
|
|
1760
|
+
]);
|
|
1761
|
+
|
|
1762
|
+
// Call 1 — produces base + exit-1.
|
|
1763
|
+
nextStartChildResult = () => ({
|
|
1764
|
+
toolResponse: "first",
|
|
1765
|
+
data: null,
|
|
1766
|
+
threadId: "child-t",
|
|
1767
|
+
baseSnapshot: {
|
|
1768
|
+
sandboxId: "sb-first",
|
|
1769
|
+
providerId: "test",
|
|
1770
|
+
data: { tag: "base" },
|
|
1771
|
+
createdAt: new Date().toISOString(),
|
|
1772
|
+
},
|
|
1773
|
+
snapshot: {
|
|
1774
|
+
sandboxId: "sb-first",
|
|
1775
|
+
providerId: "test",
|
|
1776
|
+
data: { tag: "exit-1" },
|
|
1777
|
+
createdAt: new Date().toISOString(),
|
|
1778
|
+
},
|
|
1779
|
+
});
|
|
1780
|
+
await handler(
|
|
1781
|
+
{ subagent: "snap-agent", description: "test", prompt: "first" },
|
|
1782
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1783
|
+
);
|
|
1784
|
+
|
|
1785
|
+
// Call 2 — same thread, produces exit-2 (supersedes exit-1).
|
|
1786
|
+
nextStartChildResult = () => ({
|
|
1787
|
+
toolResponse: "second",
|
|
1788
|
+
data: null,
|
|
1789
|
+
threadId: "child-t",
|
|
1790
|
+
snapshot: {
|
|
1791
|
+
sandboxId: "sb-second",
|
|
1792
|
+
providerId: "test",
|
|
1793
|
+
data: { tag: "exit-2" },
|
|
1794
|
+
createdAt: new Date().toISOString(),
|
|
1795
|
+
},
|
|
1796
|
+
});
|
|
1797
|
+
await handler(
|
|
1798
|
+
{
|
|
1799
|
+
subagent: "snap-agent",
|
|
1800
|
+
description: "test",
|
|
1801
|
+
prompt: "second",
|
|
1802
|
+
threadId: "child-t",
|
|
1803
|
+
},
|
|
1804
|
+
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1805
|
+
);
|
|
1806
|
+
|
|
1807
|
+
await cleanupSubagentSnapshots();
|
|
1808
|
+
|
|
1809
|
+
// Parent should have deleted both the latest thread snapshot and the
|
|
1810
|
+
// per-agent base snapshot through the subagent's sandbox.proxy.
|
|
1811
|
+
const deletedTags = opsMock.deleteSandboxSnapshot.mock.calls.map(
|
|
1812
|
+
(call) => (call[0] as { data: { tag: string } }).data.tag
|
|
1813
|
+
);
|
|
1814
|
+
expect(deletedTags.sort()).toEqual(["base", "exit-2"]);
|
|
1815
|
+
});
|
|
1816
|
+
|
|
1817
|
+
it("does not call deleteSandboxSnapshot for children that produced no snapshots", async () => {
|
|
1818
|
+
const opsMock = makeMockSandboxOps();
|
|
1819
|
+
const config: SubagentConfig = {
|
|
1820
|
+
agentName: "snap-agent",
|
|
1821
|
+
description: "Snapshot-driven",
|
|
1822
|
+
workflow: mockWorkflow(),
|
|
1823
|
+
thread: "continue",
|
|
1824
|
+
sandbox: {
|
|
1825
|
+
source: "own",
|
|
1826
|
+
init: "once",
|
|
1827
|
+
continuation: "snapshot",
|
|
1828
|
+
proxy: () => opsMock,
|
|
1829
|
+
},
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1832
|
+
const { handler, cleanupSubagentSnapshots } = createSubagentHandler([
|
|
1833
|
+
config,
|
|
1834
|
+
]);
|
|
1835
|
+
|
|
1836
|
+
nextStartChildResult = () => ({
|
|
1837
|
+
toolResponse: "no-snap",
|
|
1838
|
+
data: null,
|
|
1839
|
+
threadId: "child-t",
|
|
1840
|
+
});
|
|
1841
|
+
await handler(
|
|
1842
|
+
{ subagent: "snap-agent", description: "test", prompt: "run" },
|
|
1843
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1844
|
+
);
|
|
1845
|
+
|
|
1846
|
+
await cleanupSubagentSnapshots();
|
|
1847
|
+
|
|
1848
|
+
expect(opsMock.deleteSandboxSnapshot).not.toHaveBeenCalled();
|
|
1849
|
+
});
|
|
1527
1850
|
});
|
|
1528
1851
|
|
|
1529
1852
|
// ---------------------------------------------------------------------------
|
|
@@ -1865,64 +2188,23 @@ describe("defineSubagentWorkflow", () => {
|
|
|
1865
2188
|
});
|
|
1866
2189
|
});
|
|
1867
2190
|
|
|
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
2191
|
it("uses keep-until-parent-close from workflowInput override in sessionInput", async () => {
|
|
1911
2192
|
let capturedSession: SubagentSessionInput | undefined;
|
|
1912
2193
|
const workflow = defineSubagentWorkflow(
|
|
1913
2194
|
{ name: "test", description: "test agent" },
|
|
1914
2195
|
async (_prompt, sessionInput) => {
|
|
1915
2196
|
capturedSession = sessionInput;
|
|
1916
|
-
return {
|
|
2197
|
+
return {
|
|
2198
|
+
toolResponse: "ok",
|
|
2199
|
+
data: null,
|
|
2200
|
+
threadId: "t",
|
|
2201
|
+
sandboxId: "sb-1",
|
|
2202
|
+
};
|
|
1917
2203
|
}
|
|
1918
2204
|
);
|
|
1919
2205
|
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
await workflow("go", { sandboxShutdown: "keep-until-parent-close" });
|
|
1923
|
-
} catch {
|
|
1924
|
-
// expected — no destroySandbox callback
|
|
1925
|
-
}
|
|
2206
|
+
await workflow("go", { sandboxShutdown: "keep-until-parent-close" });
|
|
2207
|
+
|
|
1926
2208
|
expect(capturedSession?.sandboxShutdown).toBe("keep-until-parent-close");
|
|
1927
2209
|
});
|
|
1928
2210
|
});
|