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.
- package/README.md +146 -92
- package/dist/{activities-BVI2lTwr.d.ts → activities-BKhMtKDd.d.ts} +4 -2
- package/dist/{activities-hd4aNnZE.d.cts → activities-CDcwkRZs.d.cts} +4 -2
- package/dist/adapters/sandbox/bedrock/index.cjs +17 -14
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +7 -6
- package/dist/adapters/sandbox/bedrock/index.d.ts +7 -6
- package/dist/adapters/sandbox/bedrock/index.js +17 -14
- 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 +11 -3
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +5 -4
- package/dist/adapters/sandbox/daytona/index.d.ts +5 -4
- package/dist/adapters/sandbox/daytona/index.js +11 -3
- 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 +73 -12
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +26 -4
- package/dist/adapters/sandbox/e2b/index.d.ts +26 -4
- package/dist/adapters/sandbox/e2b/index.js +73 -12
- 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 +8 -3
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +5 -4
- package/dist/adapters/sandbox/inmemory/index.d.ts +5 -4
- package/dist/adapters/sandbox/inmemory/index.js +8 -3
- 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 +94 -39
- 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 +94 -39
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +7 -2
- 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 +7 -2
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +77 -28
- 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 +77 -28
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +7 -2
- 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 +7 -2
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +57 -10
- 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 +57 -10
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +7 -2
- 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 +7 -2
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +322 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -14
- package/dist/index.d.ts +20 -14
- package/dist/index.js +323 -147
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BjdFGPTm.d.ts → proxy-CUlKSvZS.d.ts} +1 -1
- package/dist/{proxy-7RnVaPdJ.d.cts → proxy-D_3x7RN4.d.cts} +1 -1
- package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-CVu7o2cs.d.ts} +4 -2
- package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-HSwyh28L.d.cts} +4 -2
- package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-c1gPopAG.d.ts} +4 -2
- package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-wGi-LqIP.d.cts} +4 -2
- package/dist/{types-Mc_4BCfT.d.cts → types-BH_IRryz.d.ts} +10 -1
- package/dist/{types-yiXmqedU.d.ts → types-BaOw4hKI.d.cts} +10 -1
- package/dist/{types-DQ1l_gXL.d.cts → types-C06FwR96.d.cts} +121 -17
- package/dist/{types-wiGLvxWf.d.ts → types-DAsQ21Rt.d.ts} +1 -1
- package/dist/{types-CADc5V_P.d.ts → types-DNr31FzL.d.ts} +121 -17
- package/dist/{types-CBH54cwr.d.cts → types-lm8tMNJQ.d.cts} +1 -1
- package/dist/{types-DxCpFNv_.d.cts → types-yx0LzPGn.d.cts} +44 -5
- package/dist/{types-DxCpFNv_.d.ts → types-yx0LzPGn.d.ts} +44 -5
- package/dist/{workflow-DhtWRovz.d.cts → workflow-CSCkpwAL.d.ts} +2 -2
- package/dist/{workflow-P2pTSfKu.d.ts → workflow-DuvMZ8Vm.d.cts} +2 -2
- package/dist/workflow.cjs +274 -130
- 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 +275 -131
- package/dist/workflow.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
- package/src/adapters/sandbox/bedrock/index.ts +22 -11
- package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
- package/src/adapters/sandbox/daytona/index.ts +18 -3
- 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 +87 -14
- package/src/adapters/sandbox/e2b/proxy.ts +2 -0
- package/src/adapters/sandbox/e2b/types.ts +16 -0
- package/src/adapters/sandbox/inmemory/index.ts +17 -3
- package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
- package/src/adapters/thread/anthropic/activities.ts +58 -26
- package/src/adapters/thread/anthropic/model-invoker.ts +18 -7
- 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 +63 -46
- package/src/adapters/thread/google-genai/activities.ts +20 -2
- package/src/adapters/thread/google-genai/model-invoker.ts +27 -7
- 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 +57 -33
- package/src/adapters/thread/langchain/activities.ts +55 -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 +5 -4
- 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 +23 -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 +18 -3
- package/src/lib/model/proxy.ts +2 -2
- package/src/lib/model/types.ts +10 -0
- package/src/lib/observability/hooks.ts +4 -5
- package/src/lib/observability/index.ts +1 -4
- package/src/lib/sandbox/manager.ts +45 -20
- 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 +60 -6
- package/src/lib/session/session-edge-cases.integration.test.ts +316 -14
- package/src/lib/session/session.integration.test.ts +161 -1
- package/src/lib/session/session.ts +106 -21
- package/src/lib/session/types.ts +25 -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 +526 -248
- 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/manager.ts +18 -0
- package/src/lib/thread/proxy.ts +4 -4
- package/src/lib/thread/types.ts +20 -3
- package/src/lib/tool-router/index.ts +3 -5
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +93 -1
- package/src/lib/tool-router/router.integration.test.ts +12 -0
- package/src/lib/tool-router/router.ts +90 -16
- package/src/lib/tool-router/types.ts +45 -4
- 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 +22 -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,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
|
-
|
|
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
|
-
|
|
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 {
|
|
374
|
-
const
|
|
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: {
|
|
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 =
|
|
396
|
-
if (!lastCall) throw new Error("expected
|
|
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: {
|
|
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 {
|
|
424
|
-
const
|
|
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 =
|
|
446
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
453
|
-
const
|
|
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 =
|
|
474
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
481
|
-
const
|
|
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 =
|
|
498
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
505
|
-
const
|
|
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 =
|
|
527
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
536
|
-
const
|
|
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 =
|
|
558
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
568
|
-
const
|
|
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 =
|
|
590
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
600
|
-
const
|
|
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 =
|
|
621
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
630
|
-
const
|
|
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 =
|
|
652
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
659
|
-
const
|
|
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 =
|
|
701
|
-
if (!secondCall) throw new Error("expected second
|
|
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 {
|
|
715
|
-
const
|
|
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 =
|
|
752
|
-
if (!secondCall) throw new Error("expected
|
|
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
|
|
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
|
-
|
|
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
|
|
789
|
-
const
|
|
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(
|
|
810
|
+
expect(opsMock.destroySandbox).not.toHaveBeenCalled();
|
|
816
811
|
});
|
|
817
812
|
|
|
818
|
-
it("
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
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
|
|
851
|
-
const
|
|
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: {
|
|
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(
|
|
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 {
|
|
887
|
-
const
|
|
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 =
|
|
908
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
915
|
-
const
|
|
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 =
|
|
937
|
-
if (!lastCall) throw new Error("expected
|
|
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("
|
|
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
|
-
|
|
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 {
|
|
976
|
-
const
|
|
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: {
|
|
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 =
|
|
998
|
-
if (!lastCall) throw new Error("expected
|
|
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 {
|
|
1010
|
-
const
|
|
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: {
|
|
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 =
|
|
1052
|
-
if (!secondCall) throw new Error("expected
|
|
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 {
|
|
1065
|
-
const
|
|
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: {
|
|
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 =
|
|
1090
|
-
if (!firstCall) throw new Error("expected
|
|
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 =
|
|
1109
|
-
if (!secondCall) throw new Error("expected
|
|
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 {
|
|
1121
|
-
const
|
|
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: {
|
|
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 =
|
|
1157
|
-
if (!secondCall) throw new Error("expected
|
|
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("
|
|
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: {
|
|
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
|
-
|
|
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("
|
|
1329
|
-
|
|
1330
|
-
|
|
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
|
-
|
|
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
|
|
1361
|
-
const
|
|
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: {
|
|
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(
|
|
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 {
|
|
1394
|
-
const
|
|
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 =
|
|
1423
|
-
if (!firstCall) throw new Error("expected
|
|
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 {
|
|
1430
|
-
const
|
|
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 =
|
|
1459
|
-
if (!firstCall) throw new Error("expected
|
|
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 {
|
|
1466
|
-
const
|
|
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: {
|
|
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 =
|
|
1491
|
-
if (!firstCall) throw new Error("expected
|
|
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 {
|
|
1498
|
-
const
|
|
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: {
|
|
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 =
|
|
1523
|
-
if (!firstCall) throw new Error("expected
|
|
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 {
|
|
2193
|
+
return {
|
|
2194
|
+
toolResponse: "ok",
|
|
2195
|
+
data: null,
|
|
2196
|
+
threadId: "t",
|
|
2197
|
+
sandboxId: "sb-1",
|
|
2198
|
+
};
|
|
1917
2199
|
}
|
|
1918
2200
|
);
|
|
1919
2201
|
|
|
1920
|
-
|
|
1921
|
-
|
|
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
|
});
|