zeitlich 0.2.37 → 0.2.39
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 +18 -0
- package/dist/{activities-Bb-nAjwQ.d.ts → activities-Bmu7XnaG.d.ts} +4 -4
- package/dist/{activities-vkI4_3CC.d.cts → activities-ByBFLvm2.d.cts} +4 -4
- package/dist/adapter-id-BB-mmrts.d.cts +17 -0
- package/dist/adapter-id-BB-mmrts.d.ts +17 -0
- package/dist/adapter-id-CMwVrVqv.d.cts +17 -0
- package/dist/adapter-id-CMwVrVqv.d.ts +17 -0
- package/dist/adapter-id-CbY2zeSt.d.cts +17 -0
- package/dist/adapter-id-CbY2zeSt.d.ts +17 -0
- package/dist/adapters/sandbox/bedrock/index.cjs +3 -3
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +6 -6
- package/dist/adapters/sandbox/bedrock/index.d.ts +6 -6
- package/dist/adapters/sandbox/bedrock/index.js +3 -3
- package/dist/adapters/sandbox/bedrock/index.js.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/daytona/index.cjs +3 -3
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +4 -4
- package/dist/adapters/sandbox/daytona/index.d.ts +4 -4
- package/dist/adapters/sandbox/daytona/index.js +3 -3
- package/dist/adapters/sandbox/daytona/index.js.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/e2b/index.cjs +26 -14
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +24 -4
- package/dist/adapters/sandbox/e2b/index.d.ts +24 -4
- package/dist/adapters/sandbox/e2b/index.js +26 -14
- package/dist/adapters/sandbox/e2b/index.js.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/inmemory/index.cjs +3 -3
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +4 -4
- package/dist/adapters/sandbox/inmemory/index.d.ts +4 -4
- package/dist/adapters/sandbox/inmemory/index.js +3 -3
- package/dist/adapters/sandbox/inmemory/index.js.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/thread/anthropic/index.cjs +150 -13
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +9 -8
- package/dist/adapters/thread/anthropic/index.d.ts +9 -8
- package/dist/adapters/thread/anthropic/index.js +150 -14
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +9 -3
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +6 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +6 -5
- package/dist/adapters/thread/anthropic/workflow.js +9 -4
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +154 -13
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +6 -5
- package/dist/adapters/thread/google-genai/index.d.ts +6 -5
- package/dist/adapters/thread/google-genai/index.js +154 -14
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +9 -3
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +6 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +6 -5
- package/dist/adapters/thread/google-genai/workflow.js +9 -4
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/index.cjs +16 -0
- package/dist/adapters/thread/index.cjs.map +1 -0
- package/dist/adapters/thread/index.d.cts +34 -0
- package/dist/adapters/thread/index.d.ts +34 -0
- package/dist/adapters/thread/index.js +12 -0
- package/dist/adapters/thread/index.js.map +1 -0
- package/dist/adapters/thread/langchain/index.cjs +149 -14
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +9 -8
- package/dist/adapters/thread/langchain/index.d.ts +9 -8
- package/dist/adapters/thread/langchain/index.js +149 -15
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +9 -3
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +6 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +6 -5
- package/dist/adapters/thread/langchain/workflow.js +9 -4
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +367 -59
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +365 -61
- package/dist/index.js.map +1 -1
- package/dist/{proxy-DEtowJyd.d.cts → proxy-BAKzNGRq.d.cts} +1 -1
- package/dist/{proxy-0smGKvx8.d.ts → proxy-DO_MXbY4.d.ts} +1 -1
- package/dist/{thread-manager-C-C4pI2z.d.ts → thread-manager-CcRXasqs.d.ts} +2 -2
- package/dist/{thread-manager-D4vgzYrh.d.cts → thread-manager-ClwSaUnj.d.cts} +2 -2
- package/dist/{thread-manager-3fszQih4.d.ts → thread-manager-D-7lp1JK.d.ts} +2 -2
- package/dist/{thread-manager-CzYln2OC.d.cts → thread-manager-Y8Ucf0Tf.d.cts} +2 -2
- package/dist/{types-CPKDl-y_.d.ts → types-Bcbiq8iv.d.cts} +195 -22
- package/dist/{types-CNuWnvy9.d.ts → types-DAsQ21Rt.d.ts} +1 -1
- package/dist/{types-B37hKoWA.d.ts → types-DpHTX-iO.d.ts} +58 -1
- package/dist/{types-BO7Yju20.d.cts → types-Dt8-HBBT.d.ts} +195 -22
- package/dist/{types-D08CXPh8.d.cts → types-hFFi-Zd9.d.cts} +58 -1
- package/dist/{types-DWEUmYAJ.d.cts → types-lm8tMNJQ.d.cts} +1 -1
- package/dist/{types-tQL9njTu.d.cts → types-yx0LzPGn.d.cts} +21 -7
- package/dist/{types-tQL9njTu.d.ts → types-yx0LzPGn.d.ts} +21 -7
- package/dist/{workflow-CjXHbZZc.d.ts → workflow-Bmf9EtDW.d.ts} +83 -3
- package/dist/{workflow-Do_lzJpT.d.cts → workflow-Bx9utBwb.d.cts} +83 -3
- package/dist/workflow.cjs +266 -39
- 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 +264 -41
- package/dist/workflow.js.map +1 -1
- package/package.json +12 -2
- package/src/adapters/sandbox/bedrock/index.ts +12 -3
- package/src/adapters/sandbox/daytona/index.ts +12 -3
- package/src/adapters/sandbox/e2b/index.ts +36 -14
- package/src/adapters/sandbox/e2b/types.ts +16 -0
- package/src/adapters/sandbox/inmemory/index.ts +12 -3
- package/src/adapters/thread/adapter-id.test.ts +42 -0
- package/src/adapters/thread/anthropic/activities.ts +40 -5
- package/src/adapters/thread/anthropic/adapter-id.ts +16 -0
- package/src/adapters/thread/anthropic/fork-transform.test.ts +291 -0
- package/src/adapters/thread/anthropic/index.ts +3 -0
- package/src/adapters/thread/anthropic/model-invoker.ts +7 -1
- package/src/adapters/thread/anthropic/proxy.ts +3 -2
- package/src/adapters/thread/anthropic/thread-manager.ts +27 -1
- package/src/adapters/thread/google-genai/activities.ts +44 -5
- package/src/adapters/thread/google-genai/adapter-id.ts +16 -0
- package/src/adapters/thread/google-genai/fork-transform.test.ts +149 -0
- package/src/adapters/thread/google-genai/index.ts +3 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +8 -2
- package/src/adapters/thread/google-genai/proxy.ts +3 -2
- package/src/adapters/thread/google-genai/thread-manager.ts +27 -1
- package/src/adapters/thread/index.ts +39 -0
- package/src/adapters/thread/langchain/activities.ts +40 -5
- package/src/adapters/thread/langchain/adapter-id.ts +16 -0
- package/src/adapters/thread/langchain/fork-transform.test.ts +142 -0
- package/src/adapters/thread/langchain/index.ts +3 -0
- package/src/adapters/thread/langchain/model-invoker.ts +7 -1
- package/src/adapters/thread/langchain/proxy.ts +3 -2
- package/src/adapters/thread/langchain/thread-manager.ts +27 -1
- package/src/lib/lifecycle.ts +14 -5
- package/src/lib/model/types.ts +7 -0
- package/src/lib/sandbox/manager.ts +26 -18
- package/src/lib/sandbox/types.ts +27 -7
- package/src/lib/session/session-edge-cases.integration.test.ts +336 -4
- package/src/lib/session/session.integration.test.ts +192 -2
- package/src/lib/session/session.ts +102 -8
- package/src/lib/session/types.ts +66 -3
- package/src/lib/state/index.ts +1 -0
- package/src/lib/state/manager.integration.test.ts +109 -0
- package/src/lib/state/manager.ts +38 -8
- package/src/lib/state/types.ts +25 -0
- package/src/lib/subagent/handler.ts +124 -11
- package/src/lib/subagent/index.ts +5 -1
- package/src/lib/subagent/subagent.integration.test.ts +628 -104
- package/src/lib/subagent/types.ts +63 -14
- package/src/lib/subagent/workflow.ts +29 -2
- package/src/lib/thread/index.ts +5 -0
- package/src/lib/thread/keys.test.ts +101 -0
- package/src/lib/thread/keys.ts +94 -0
- package/src/lib/thread/manager.test.ts +139 -0
- package/src/lib/thread/manager.ts +105 -9
- package/src/lib/thread/proxy.ts +3 -0
- package/src/lib/thread/types.ts +64 -1
- package/src/lib/tool-router/index.ts +2 -0
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +92 -0
- package/src/lib/tool-router/router.integration.test.ts +12 -0
- package/src/lib/tool-router/router.ts +89 -16
- package/src/lib/tool-router/types.ts +42 -1
- package/src/lib/types.ts +12 -0
- package/src/workflow.ts +14 -1
- package/tsup.config.ts +1 -0
|
@@ -36,7 +36,7 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
36
36
|
condition: vi.fn(async (fn: () => boolean) => {
|
|
37
37
|
if (!fn()) throw new Error("condition predicate was not satisfied");
|
|
38
38
|
}),
|
|
39
|
-
|
|
39
|
+
executeChild: vi.fn(
|
|
40
40
|
async (
|
|
41
41
|
_workflow: unknown,
|
|
42
42
|
opts: { workflowId: string; args: unknown[] }
|
|
@@ -51,11 +51,7 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
51
51
|
usage: { inputTokens: 100, outputTokens: 50 },
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
return
|
|
55
|
-
signal: vi.fn(),
|
|
56
|
-
result: () => Promise.resolve(result),
|
|
57
|
-
workflowId: opts.workflowId,
|
|
58
|
-
};
|
|
54
|
+
return result;
|
|
59
55
|
}
|
|
60
56
|
),
|
|
61
57
|
getExternalWorkflowHandle: vi.fn((_id: string) => ({
|
|
@@ -378,8 +374,8 @@ describe("createSubagentHandler", () => {
|
|
|
378
374
|
});
|
|
379
375
|
|
|
380
376
|
it("passes sandbox inherit to child when sandbox is inherit", async () => {
|
|
381
|
-
const {
|
|
382
|
-
const
|
|
377
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
378
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
383
379
|
|
|
384
380
|
const inheritSubagent: SubagentConfig = {
|
|
385
381
|
agentName: "inherit-agent",
|
|
@@ -404,8 +400,8 @@ describe("createSubagentHandler", () => {
|
|
|
404
400
|
}
|
|
405
401
|
);
|
|
406
402
|
|
|
407
|
-
const lastCall =
|
|
408
|
-
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");
|
|
409
405
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
410
406
|
expect(workflowInput.sandbox).toEqual({
|
|
411
407
|
mode: "inherit",
|
|
@@ -436,8 +432,8 @@ describe("createSubagentHandler", () => {
|
|
|
436
432
|
});
|
|
437
433
|
|
|
438
434
|
it("does not pass sandboxId to child when sandbox is own (first call)", async () => {
|
|
439
|
-
const {
|
|
440
|
-
const
|
|
435
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
436
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
441
437
|
|
|
442
438
|
const ownSubagent: SubagentConfig = {
|
|
443
439
|
agentName: "own-agent",
|
|
@@ -458,15 +454,15 @@ describe("createSubagentHandler", () => {
|
|
|
458
454
|
}
|
|
459
455
|
);
|
|
460
456
|
|
|
461
|
-
const lastCall =
|
|
462
|
-
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");
|
|
463
459
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
464
460
|
expect(workflowInput.sandbox).toBeUndefined();
|
|
465
461
|
});
|
|
466
462
|
|
|
467
463
|
it("resolves context function at invocation time", async () => {
|
|
468
|
-
const {
|
|
469
|
-
const
|
|
464
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
465
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
470
466
|
|
|
471
467
|
let counter = 0;
|
|
472
468
|
const dynamicSubagent: SubagentConfig = {
|
|
@@ -486,15 +482,15 @@ describe("createSubagentHandler", () => {
|
|
|
486
482
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
487
483
|
);
|
|
488
484
|
|
|
489
|
-
const lastCall =
|
|
490
|
-
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");
|
|
491
487
|
const context = lastCall[1].args[2] as Record<string, unknown>;
|
|
492
488
|
expect(context).toEqual({ invocation: 1 });
|
|
493
489
|
});
|
|
494
490
|
|
|
495
491
|
it("passes static context unchanged", async () => {
|
|
496
|
-
const {
|
|
497
|
-
const
|
|
492
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
493
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
498
494
|
|
|
499
495
|
const staticSubagent: SubagentConfig = {
|
|
500
496
|
agentName: "static-ctx",
|
|
@@ -510,15 +506,15 @@ describe("createSubagentHandler", () => {
|
|
|
510
506
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
511
507
|
);
|
|
512
508
|
|
|
513
|
-
const lastCall =
|
|
514
|
-
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");
|
|
515
511
|
const context = lastCall[1].args[2] as Record<string, unknown>;
|
|
516
512
|
expect(context).toEqual({ key: "value" });
|
|
517
513
|
});
|
|
518
514
|
|
|
519
515
|
it("does not pass sandbox init when sandbox is own without prior sandbox", async () => {
|
|
520
|
-
const {
|
|
521
|
-
const
|
|
516
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
517
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
522
518
|
|
|
523
519
|
const ownSubagent: SubagentConfig = {
|
|
524
520
|
agentName: "own-agent",
|
|
@@ -539,8 +535,8 @@ describe("createSubagentHandler", () => {
|
|
|
539
535
|
}
|
|
540
536
|
);
|
|
541
537
|
|
|
542
|
-
const lastCall =
|
|
543
|
-
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");
|
|
544
540
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
545
541
|
expect(workflowInput.sandbox).toBeUndefined();
|
|
546
542
|
});
|
|
@@ -548,8 +544,8 @@ describe("createSubagentHandler", () => {
|
|
|
548
544
|
// --- Thread mode ---
|
|
549
545
|
|
|
550
546
|
it("passes thread fork when thread is fork and threadId provided", async () => {
|
|
551
|
-
const {
|
|
552
|
-
const
|
|
547
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
548
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
553
549
|
|
|
554
550
|
const contSubagent: SubagentConfig = {
|
|
555
551
|
agentName: "cont",
|
|
@@ -570,8 +566,8 @@ describe("createSubagentHandler", () => {
|
|
|
570
566
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
571
567
|
);
|
|
572
568
|
|
|
573
|
-
const lastCall =
|
|
574
|
-
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");
|
|
575
571
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
576
572
|
expect(workflowInput.thread).toEqual({
|
|
577
573
|
mode: "fork",
|
|
@@ -580,8 +576,8 @@ describe("createSubagentHandler", () => {
|
|
|
580
576
|
});
|
|
581
577
|
|
|
582
578
|
it("passes thread continue when thread is continue", async () => {
|
|
583
|
-
const {
|
|
584
|
-
const
|
|
579
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
580
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
585
581
|
|
|
586
582
|
const contSubagent: SubagentConfig = {
|
|
587
583
|
agentName: "cont-mode",
|
|
@@ -602,8 +598,8 @@ describe("createSubagentHandler", () => {
|
|
|
602
598
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
603
599
|
);
|
|
604
600
|
|
|
605
|
-
const lastCall =
|
|
606
|
-
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");
|
|
607
603
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
608
604
|
expect(workflowInput.thread).toEqual({
|
|
609
605
|
mode: "continue",
|
|
@@ -612,8 +608,8 @@ describe("createSubagentHandler", () => {
|
|
|
612
608
|
});
|
|
613
609
|
|
|
614
610
|
it("does not pass thread when thread is new", async () => {
|
|
615
|
-
const {
|
|
616
|
-
const
|
|
611
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
612
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
617
613
|
|
|
618
614
|
const noContSubagent: SubagentConfig = {
|
|
619
615
|
agentName: "no-cont",
|
|
@@ -633,8 +629,8 @@ describe("createSubagentHandler", () => {
|
|
|
633
629
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
634
630
|
);
|
|
635
631
|
|
|
636
|
-
const lastCall =
|
|
637
|
-
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");
|
|
638
634
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
639
635
|
expect(workflowInput.thread).toBeUndefined();
|
|
640
636
|
});
|
|
@@ -642,8 +638,8 @@ describe("createSubagentHandler", () => {
|
|
|
642
638
|
// --- Sandbox continuation ---
|
|
643
639
|
|
|
644
640
|
it("does not pass sandbox when thread is fork (own sandbox)", async () => {
|
|
645
|
-
const {
|
|
646
|
-
const
|
|
641
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
642
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
647
643
|
|
|
648
644
|
const contSandboxSubagent: SubagentConfig = {
|
|
649
645
|
agentName: "sb-cont",
|
|
@@ -664,15 +660,15 @@ describe("createSubagentHandler", () => {
|
|
|
664
660
|
}
|
|
665
661
|
);
|
|
666
662
|
|
|
667
|
-
const lastCall =
|
|
668
|
-
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");
|
|
669
665
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
670
666
|
expect(workflowInput.sandbox).toBeUndefined();
|
|
671
667
|
});
|
|
672
668
|
|
|
673
669
|
it("tracks sandbox ID and passes sandbox fork on continuation", async () => {
|
|
674
|
-
const {
|
|
675
|
-
const
|
|
670
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
671
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
676
672
|
|
|
677
673
|
nextStartChildResult = () => ({
|
|
678
674
|
toolResponse: "first run done",
|
|
@@ -713,8 +709,8 @@ describe("createSubagentHandler", () => {
|
|
|
713
709
|
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
714
710
|
);
|
|
715
711
|
|
|
716
|
-
const secondCall =
|
|
717
|
-
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");
|
|
718
714
|
const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
719
715
|
expect(workflowInput.thread).toEqual({
|
|
720
716
|
mode: "fork",
|
|
@@ -727,8 +723,8 @@ describe("createSubagentHandler", () => {
|
|
|
727
723
|
});
|
|
728
724
|
|
|
729
725
|
it("does not pass sandbox fork without thread continuation", async () => {
|
|
730
|
-
const {
|
|
731
|
-
const
|
|
726
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
727
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
732
728
|
|
|
733
729
|
nextStartChildResult = () => ({
|
|
734
730
|
toolResponse: "done",
|
|
@@ -764,8 +760,8 @@ describe("createSubagentHandler", () => {
|
|
|
764
760
|
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
765
761
|
);
|
|
766
762
|
|
|
767
|
-
const secondCall =
|
|
768
|
-
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");
|
|
769
765
|
const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
770
766
|
expect(workflowInput.sandbox).toBeUndefined();
|
|
771
767
|
expect(workflowInput.thread).toBeUndefined();
|
|
@@ -882,8 +878,8 @@ describe("createSubagentHandler", () => {
|
|
|
882
878
|
});
|
|
883
879
|
|
|
884
880
|
it("does not pass sandboxId when sandbox is none (default)", async () => {
|
|
885
|
-
const {
|
|
886
|
-
const
|
|
881
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
882
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
887
883
|
|
|
888
884
|
const noneSubagent: SubagentConfig = {
|
|
889
885
|
agentName: "none-agent",
|
|
@@ -903,15 +899,15 @@ describe("createSubagentHandler", () => {
|
|
|
903
899
|
}
|
|
904
900
|
);
|
|
905
901
|
|
|
906
|
-
const lastCall =
|
|
907
|
-
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");
|
|
908
904
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
909
905
|
expect(workflowInput.sandbox).toBeUndefined();
|
|
910
906
|
});
|
|
911
907
|
|
|
912
908
|
it("does not pass sandboxId when sandbox is explicitly none", async () => {
|
|
913
|
-
const {
|
|
914
|
-
const
|
|
909
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
910
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
915
911
|
|
|
916
912
|
const noneSubagent: SubagentConfig = {
|
|
917
913
|
agentName: "none-agent",
|
|
@@ -932,8 +928,8 @@ describe("createSubagentHandler", () => {
|
|
|
932
928
|
}
|
|
933
929
|
);
|
|
934
930
|
|
|
935
|
-
const lastCall =
|
|
936
|
-
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");
|
|
937
933
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
938
934
|
expect(workflowInput.sandbox).toBeUndefined();
|
|
939
935
|
});
|
|
@@ -961,8 +957,8 @@ describe("createSubagentHandler", () => {
|
|
|
961
957
|
// --- inherit + continuation: fork ---
|
|
962
958
|
|
|
963
959
|
it("forks from parent sandbox when inherit + continuation=fork", async () => {
|
|
964
|
-
const {
|
|
965
|
-
const
|
|
960
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
961
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
966
962
|
|
|
967
963
|
const config: SubagentConfig = {
|
|
968
964
|
agentName: "inherit-fork",
|
|
@@ -987,8 +983,8 @@ describe("createSubagentHandler", () => {
|
|
|
987
983
|
}
|
|
988
984
|
);
|
|
989
985
|
|
|
990
|
-
const lastCall =
|
|
991
|
-
if (!lastCall) throw new Error("expected
|
|
986
|
+
const lastCall = execMock.mock.calls.at(-1);
|
|
987
|
+
if (!lastCall) throw new Error("expected executeChild call");
|
|
992
988
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
993
989
|
expect(workflowInput.sandbox).toEqual({
|
|
994
990
|
mode: "fork",
|
|
@@ -999,8 +995,8 @@ describe("createSubagentHandler", () => {
|
|
|
999
995
|
// --- own + continuation: continue ---
|
|
1000
996
|
|
|
1001
997
|
it("passes sandbox continue on thread continuation with continuation=continue", async () => {
|
|
1002
|
-
const {
|
|
1003
|
-
const
|
|
998
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
999
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1004
1000
|
|
|
1005
1001
|
nextStartChildResult = () => ({
|
|
1006
1002
|
toolResponse: "first",
|
|
@@ -1045,8 +1041,8 @@ describe("createSubagentHandler", () => {
|
|
|
1045
1041
|
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1046
1042
|
);
|
|
1047
1043
|
|
|
1048
|
-
const secondCall =
|
|
1049
|
-
if (!secondCall) throw new Error("expected
|
|
1044
|
+
const secondCall = execMock.mock.calls.at(-1);
|
|
1045
|
+
if (!secondCall) throw new Error("expected executeChild call");
|
|
1050
1046
|
const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1051
1047
|
expect(workflowInput.sandbox).toEqual({
|
|
1052
1048
|
mode: "continue",
|
|
@@ -1058,8 +1054,8 @@ describe("createSubagentHandler", () => {
|
|
|
1058
1054
|
// --- own + init: once + continuation: fork ---
|
|
1059
1055
|
|
|
1060
1056
|
it("stores sandbox on first call and forks from it on second call (init=once, continuation=fork)", async () => {
|
|
1061
|
-
const {
|
|
1062
|
-
const
|
|
1057
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1058
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1063
1059
|
|
|
1064
1060
|
nextStartChildResult = () => ({
|
|
1065
1061
|
toolResponse: "first",
|
|
@@ -1088,8 +1084,8 @@ describe("createSubagentHandler", () => {
|
|
|
1088
1084
|
);
|
|
1089
1085
|
|
|
1090
1086
|
// First call: no sandbox init (child creates fresh), forced pause-until-parent-close
|
|
1091
|
-
const firstCall =
|
|
1092
|
-
if (!firstCall) throw new Error("expected
|
|
1087
|
+
const firstCall = execMock.mock.calls.at(-1);
|
|
1088
|
+
if (!firstCall) throw new Error("expected executeChild call");
|
|
1093
1089
|
const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
|
|
1094
1090
|
expect(firstInput.sandbox).toBeUndefined();
|
|
1095
1091
|
expect(firstInput.sandboxShutdown).toBe("pause-until-parent-close");
|
|
@@ -1107,8 +1103,8 @@ describe("createSubagentHandler", () => {
|
|
|
1107
1103
|
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1108
1104
|
);
|
|
1109
1105
|
|
|
1110
|
-
const secondCall =
|
|
1111
|
-
if (!secondCall) throw new Error("expected
|
|
1106
|
+
const secondCall = execMock.mock.calls.at(-1);
|
|
1107
|
+
if (!secondCall) throw new Error("expected executeChild call");
|
|
1112
1108
|
const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1113
1109
|
expect(secondInput.sandbox).toEqual({
|
|
1114
1110
|
mode: "fork",
|
|
@@ -1119,8 +1115,8 @@ describe("createSubagentHandler", () => {
|
|
|
1119
1115
|
// --- own + init: once + continuation: continue ---
|
|
1120
1116
|
|
|
1121
1117
|
it("stores sandbox on first call and continues it on second call (init=once, continuation=continue)", async () => {
|
|
1122
|
-
const {
|
|
1123
|
-
const
|
|
1118
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1119
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1124
1120
|
|
|
1125
1121
|
nextStartChildResult = () => ({
|
|
1126
1122
|
toolResponse: "first",
|
|
@@ -1160,8 +1156,8 @@ describe("createSubagentHandler", () => {
|
|
|
1160
1156
|
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1161
1157
|
);
|
|
1162
1158
|
|
|
1163
|
-
const secondCall =
|
|
1164
|
-
if (!secondCall) throw new Error("expected
|
|
1159
|
+
const secondCall = execMock.mock.calls.at(-1);
|
|
1160
|
+
if (!secondCall) throw new Error("expected executeChild call");
|
|
1165
1161
|
const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1166
1162
|
expect(secondInput.sandbox).toEqual({
|
|
1167
1163
|
mode: "continue",
|
|
@@ -1398,8 +1394,8 @@ describe("createSubagentHandler", () => {
|
|
|
1398
1394
|
// --- mustSurvive does not override user shutdown ---
|
|
1399
1395
|
|
|
1400
1396
|
it("does not override keep-until-parent-close with pause-until-parent-close for init=once", async () => {
|
|
1401
|
-
const {
|
|
1402
|
-
const
|
|
1397
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1398
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1403
1399
|
|
|
1404
1400
|
nextStartChildResult = () => ({
|
|
1405
1401
|
toolResponse: "first",
|
|
@@ -1428,15 +1424,15 @@ describe("createSubagentHandler", () => {
|
|
|
1428
1424
|
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1429
1425
|
);
|
|
1430
1426
|
|
|
1431
|
-
const firstCall =
|
|
1432
|
-
if (!firstCall) throw new Error("expected
|
|
1427
|
+
const firstCall = execMock.mock.calls.at(-1);
|
|
1428
|
+
if (!firstCall) throw new Error("expected executeChild call");
|
|
1433
1429
|
const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
|
|
1434
1430
|
expect(firstInput.sandboxShutdown).toBe("keep-until-parent-close");
|
|
1435
1431
|
});
|
|
1436
1432
|
|
|
1437
1433
|
it("does not override pause with pause-until-parent-close for init=once", async () => {
|
|
1438
|
-
const {
|
|
1439
|
-
const
|
|
1434
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1435
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1440
1436
|
|
|
1441
1437
|
nextStartChildResult = () => ({
|
|
1442
1438
|
toolResponse: "first",
|
|
@@ -1465,15 +1461,15 @@ describe("createSubagentHandler", () => {
|
|
|
1465
1461
|
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1466
1462
|
);
|
|
1467
1463
|
|
|
1468
|
-
const firstCall =
|
|
1469
|
-
if (!firstCall) throw new Error("expected
|
|
1464
|
+
const firstCall = execMock.mock.calls.at(-1);
|
|
1465
|
+
if (!firstCall) throw new Error("expected executeChild call");
|
|
1470
1466
|
const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
|
|
1471
1467
|
expect(firstInput.sandboxShutdown).toBe("pause");
|
|
1472
1468
|
});
|
|
1473
1469
|
|
|
1474
1470
|
it("does not override keep with pause for continuation=continue", async () => {
|
|
1475
|
-
const {
|
|
1476
|
-
const
|
|
1471
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1472
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1477
1473
|
|
|
1478
1474
|
nextStartChildResult = () => ({
|
|
1479
1475
|
toolResponse: "first",
|
|
@@ -1502,15 +1498,15 @@ describe("createSubagentHandler", () => {
|
|
|
1502
1498
|
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1503
1499
|
);
|
|
1504
1500
|
|
|
1505
|
-
const firstCall =
|
|
1506
|
-
if (!firstCall) throw new Error("expected
|
|
1501
|
+
const firstCall = execMock.mock.calls.at(-1);
|
|
1502
|
+
if (!firstCall) throw new Error("expected executeChild call");
|
|
1507
1503
|
const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
|
|
1508
1504
|
expect(firstInput.sandboxShutdown).toBe("keep");
|
|
1509
1505
|
});
|
|
1510
1506
|
|
|
1511
1507
|
it("still overrides destroy with pause for continuation=continue", async () => {
|
|
1512
|
-
const {
|
|
1513
|
-
const
|
|
1508
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1509
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1514
1510
|
|
|
1515
1511
|
nextStartChildResult = () => ({
|
|
1516
1512
|
toolResponse: "first",
|
|
@@ -1539,8 +1535,8 @@ describe("createSubagentHandler", () => {
|
|
|
1539
1535
|
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1540
1536
|
);
|
|
1541
1537
|
|
|
1542
|
-
const firstCall =
|
|
1543
|
-
if (!firstCall) throw new Error("expected
|
|
1538
|
+
const firstCall = execMock.mock.calls.at(-1);
|
|
1539
|
+
if (!firstCall) throw new Error("expected executeChild call");
|
|
1544
1540
|
const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
|
|
1545
1541
|
expect(firstInput.sandboxShutdown).toBe("pause");
|
|
1546
1542
|
});
|
|
@@ -1548,8 +1544,8 @@ describe("createSubagentHandler", () => {
|
|
|
1548
1544
|
// --- snapshot continuation ---
|
|
1549
1545
|
|
|
1550
1546
|
it("forces sandboxShutdown=snapshot and passes no sandbox on first call (continuation=snapshot)", async () => {
|
|
1551
|
-
const {
|
|
1552
|
-
const
|
|
1547
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1548
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1553
1549
|
|
|
1554
1550
|
nextStartChildResult = () => ({
|
|
1555
1551
|
toolResponse: "first",
|
|
@@ -1589,16 +1585,16 @@ describe("createSubagentHandler", () => {
|
|
|
1589
1585
|
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1590
1586
|
);
|
|
1591
1587
|
|
|
1592
|
-
const firstCall =
|
|
1593
|
-
if (!firstCall) throw new Error("expected
|
|
1588
|
+
const firstCall = execMock.mock.calls.at(-1);
|
|
1589
|
+
if (!firstCall) throw new Error("expected executeChild call");
|
|
1594
1590
|
const firstInput = firstCall[1].args[1] as SubagentWorkflowInput;
|
|
1595
1591
|
expect(firstInput.sandbox).toBeUndefined();
|
|
1596
1592
|
expect(firstInput.sandboxShutdown).toBe("snapshot");
|
|
1597
1593
|
});
|
|
1598
1594
|
|
|
1599
1595
|
it("boots follow-up from stored thread snapshot on continuation=snapshot", async () => {
|
|
1600
|
-
const {
|
|
1601
|
-
const
|
|
1596
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1597
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1602
1598
|
|
|
1603
1599
|
nextStartChildResult = () => ({
|
|
1604
1600
|
toolResponse: "first",
|
|
@@ -1660,8 +1656,8 @@ describe("createSubagentHandler", () => {
|
|
|
1660
1656
|
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1661
1657
|
);
|
|
1662
1658
|
|
|
1663
|
-
const secondCall =
|
|
1664
|
-
if (!secondCall) throw new Error("expected
|
|
1659
|
+
const secondCall = execMock.mock.calls.at(-1);
|
|
1660
|
+
if (!secondCall) throw new Error("expected executeChild call");
|
|
1665
1661
|
const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1666
1662
|
expect(secondInput.sandbox).toEqual({
|
|
1667
1663
|
mode: "from-snapshot",
|
|
@@ -1671,8 +1667,8 @@ describe("createSubagentHandler", () => {
|
|
|
1671
1667
|
});
|
|
1672
1668
|
|
|
1673
1669
|
it("uses per-agent base snapshot for a new thread when init=once + continuation=snapshot", async () => {
|
|
1674
|
-
const {
|
|
1675
|
-
const
|
|
1670
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
1671
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1676
1672
|
|
|
1677
1673
|
// First call — establishes base snapshot.
|
|
1678
1674
|
nextStartChildResult = () => ({
|
|
@@ -1731,8 +1727,8 @@ describe("createSubagentHandler", () => {
|
|
|
1731
1727
|
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1732
1728
|
);
|
|
1733
1729
|
|
|
1734
|
-
const secondCall =
|
|
1735
|
-
if (!secondCall) throw new Error("expected
|
|
1730
|
+
const secondCall = execMock.mock.calls.at(-1);
|
|
1731
|
+
if (!secondCall) throw new Error("expected executeChild call");
|
|
1736
1732
|
const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1737
1733
|
expect(secondInput.sandbox).toEqual({
|
|
1738
1734
|
mode: "from-snapshot",
|
|
@@ -1814,6 +1810,173 @@ describe("createSubagentHandler", () => {
|
|
|
1814
1810
|
expect(deletedTags.sort()).toEqual(["base", "exit-2"]);
|
|
1815
1811
|
});
|
|
1816
1812
|
|
|
1813
|
+
it("publishes persistentBaseSnapshot from childSandboxReadySignal before child completes", async () => {
|
|
1814
|
+
const { setHandler, executeChild } = await import("@temporalio/workflow");
|
|
1815
|
+
const setHandlerMock = setHandler as ReturnType<typeof vi.fn>;
|
|
1816
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1817
|
+
|
|
1818
|
+
const signalBase = {
|
|
1819
|
+
sandboxId: "sb-signal",
|
|
1820
|
+
providerId: "test",
|
|
1821
|
+
data: { tag: "signal-base" },
|
|
1822
|
+
createdAt: new Date().toISOString(),
|
|
1823
|
+
};
|
|
1824
|
+
|
|
1825
|
+
const config: SubagentConfig = {
|
|
1826
|
+
agentName: "snap-agent",
|
|
1827
|
+
description: "Snapshot-driven",
|
|
1828
|
+
workflow: mockWorkflow(),
|
|
1829
|
+
thread: "continue",
|
|
1830
|
+
sandbox: {
|
|
1831
|
+
source: "own",
|
|
1832
|
+
init: "once",
|
|
1833
|
+
continuation: "snapshot",
|
|
1834
|
+
proxy: noopSandboxProxy,
|
|
1835
|
+
},
|
|
1836
|
+
};
|
|
1837
|
+
|
|
1838
|
+
const { handler } = createSubagentHandler([config]);
|
|
1839
|
+
|
|
1840
|
+
// Fire the signal from inside executeChild so persistentBaseSnapshot is
|
|
1841
|
+
// set before the child's own result is returned. The child result itself
|
|
1842
|
+
// intentionally omits baseSnapshot — only the signal path can populate
|
|
1843
|
+
// the map for this call.
|
|
1844
|
+
execMock.mockImplementationOnce(
|
|
1845
|
+
async (_wf: unknown, opts: { workflowId: string; args: unknown[] }) => {
|
|
1846
|
+
const reg = setHandlerMock.mock.calls
|
|
1847
|
+
.filter(
|
|
1848
|
+
([sig]) => (sig as { name?: string })?.name === "childSandboxReady"
|
|
1849
|
+
)
|
|
1850
|
+
.at(-1);
|
|
1851
|
+
const signalHandler = reg?.[1] as
|
|
1852
|
+
| ((p: {
|
|
1853
|
+
childWorkflowId: string;
|
|
1854
|
+
sandboxId: string;
|
|
1855
|
+
baseSnapshot?: unknown;
|
|
1856
|
+
}) => void)
|
|
1857
|
+
| undefined;
|
|
1858
|
+
signalHandler?.({
|
|
1859
|
+
childWorkflowId: opts.workflowId,
|
|
1860
|
+
sandboxId: "sb-signal",
|
|
1861
|
+
baseSnapshot: signalBase,
|
|
1862
|
+
});
|
|
1863
|
+
return {
|
|
1864
|
+
toolResponse: "first",
|
|
1865
|
+
data: null,
|
|
1866
|
+
threadId: "child-sig-1",
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
);
|
|
1870
|
+
|
|
1871
|
+
await handler(
|
|
1872
|
+
{ subagent: "snap-agent", description: "test", prompt: "first" },
|
|
1873
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1874
|
+
);
|
|
1875
|
+
|
|
1876
|
+
// Second call on a new thread must boot from the signal-published base.
|
|
1877
|
+
nextStartChildResult = () => ({
|
|
1878
|
+
toolResponse: "second",
|
|
1879
|
+
data: null,
|
|
1880
|
+
threadId: "child-sig-2",
|
|
1881
|
+
});
|
|
1882
|
+
|
|
1883
|
+
await handler(
|
|
1884
|
+
{ subagent: "snap-agent", description: "test", prompt: "second" },
|
|
1885
|
+
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1886
|
+
);
|
|
1887
|
+
|
|
1888
|
+
const secondCall = execMock.mock.calls.at(-1);
|
|
1889
|
+
if (!secondCall) throw new Error("expected executeChild call");
|
|
1890
|
+
const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1891
|
+
expect(secondInput.sandbox).toEqual({
|
|
1892
|
+
mode: "from-snapshot",
|
|
1893
|
+
snapshot: expect.objectContaining({ data: { tag: "signal-base" } }),
|
|
1894
|
+
});
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
it("ignores signal baseSnapshot for non-snapshot-base creators", async () => {
|
|
1898
|
+
const { setHandler, executeChild } = await import("@temporalio/workflow");
|
|
1899
|
+
const setHandlerMock = setHandler as ReturnType<typeof vi.fn>;
|
|
1900
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
1901
|
+
|
|
1902
|
+
const config: SubagentConfig = {
|
|
1903
|
+
agentName: "lazy-fork-agent",
|
|
1904
|
+
description: "Lazy fork (not snapshot)",
|
|
1905
|
+
workflow: mockWorkflow(),
|
|
1906
|
+
sandbox: {
|
|
1907
|
+
source: "own",
|
|
1908
|
+
init: "once",
|
|
1909
|
+
continuation: "fork",
|
|
1910
|
+
proxy: noopSandboxProxy,
|
|
1911
|
+
},
|
|
1912
|
+
};
|
|
1913
|
+
|
|
1914
|
+
const { handler } = createSubagentHandler([config]);
|
|
1915
|
+
|
|
1916
|
+
execMock.mockImplementationOnce(
|
|
1917
|
+
async (_wf: unknown, opts: { workflowId: string; args: unknown[] }) => {
|
|
1918
|
+
const reg = setHandlerMock.mock.calls
|
|
1919
|
+
.filter(
|
|
1920
|
+
([sig]) => (sig as { name?: string })?.name === "childSandboxReady"
|
|
1921
|
+
)
|
|
1922
|
+
.at(-1);
|
|
1923
|
+
const signalHandler = reg?.[1] as
|
|
1924
|
+
| ((p: {
|
|
1925
|
+
childWorkflowId: string;
|
|
1926
|
+
sandboxId: string;
|
|
1927
|
+
baseSnapshot?: unknown;
|
|
1928
|
+
}) => void)
|
|
1929
|
+
| undefined;
|
|
1930
|
+
// Stray baseSnapshot on a non-snapshot path must not land in
|
|
1931
|
+
// persistentBaseSnapshot, or it would corrupt a different agent's
|
|
1932
|
+
// snapshot flow.
|
|
1933
|
+
signalHandler?.({
|
|
1934
|
+
childWorkflowId: opts.workflowId,
|
|
1935
|
+
sandboxId: "sb-lazy",
|
|
1936
|
+
baseSnapshot: {
|
|
1937
|
+
sandboxId: "sb-lazy",
|
|
1938
|
+
providerId: "test",
|
|
1939
|
+
data: { tag: "should-be-ignored" },
|
|
1940
|
+
createdAt: new Date().toISOString(),
|
|
1941
|
+
},
|
|
1942
|
+
});
|
|
1943
|
+
return {
|
|
1944
|
+
toolResponse: "first",
|
|
1945
|
+
data: null,
|
|
1946
|
+
threadId: "child-lazy-1",
|
|
1947
|
+
sandboxId: "sb-lazy",
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
);
|
|
1951
|
+
|
|
1952
|
+
await handler(
|
|
1953
|
+
{ subagent: "lazy-fork-agent", description: "test", prompt: "first" },
|
|
1954
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
1955
|
+
);
|
|
1956
|
+
|
|
1957
|
+
// Second call should fork from the lazy-published sandbox, not restore
|
|
1958
|
+
// from any snapshot.
|
|
1959
|
+
nextStartChildResult = () => ({
|
|
1960
|
+
toolResponse: "second",
|
|
1961
|
+
data: null,
|
|
1962
|
+
threadId: "child-lazy-2",
|
|
1963
|
+
sandboxId: "sb-lazy",
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
await handler(
|
|
1967
|
+
{ subagent: "lazy-fork-agent", description: "test", prompt: "second" },
|
|
1968
|
+
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
1969
|
+
);
|
|
1970
|
+
|
|
1971
|
+
const secondCall = execMock.mock.calls.at(-1);
|
|
1972
|
+
if (!secondCall) throw new Error("expected executeChild call");
|
|
1973
|
+
const secondInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
1974
|
+
expect(secondInput.sandbox).toEqual({
|
|
1975
|
+
mode: "fork",
|
|
1976
|
+
sandboxId: "sb-lazy",
|
|
1977
|
+
});
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1817
1980
|
it("does not call deleteSandboxSnapshot for children that produced no snapshots", async () => {
|
|
1818
1981
|
const opsMock = makeMockSandboxOps();
|
|
1819
1982
|
const config: SubagentConfig = {
|
|
@@ -1847,6 +2010,195 @@ describe("createSubagentHandler", () => {
|
|
|
1847
2010
|
|
|
1848
2011
|
expect(opsMock.deleteSandboxSnapshot).not.toHaveBeenCalled();
|
|
1849
2012
|
});
|
|
2013
|
+
|
|
2014
|
+
// -------------------------------------------------------------------------
|
|
2015
|
+
// Child workflow failure propagation
|
|
2016
|
+
// -------------------------------------------------------------------------
|
|
2017
|
+
|
|
2018
|
+
it("propagates executeChild failure to the caller instead of hanging", async () => {
|
|
2019
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
2020
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
2021
|
+
execMock.mockImplementationOnce(async () => {
|
|
2022
|
+
throw new Error("Child Workflow execution failed: timeout");
|
|
2023
|
+
});
|
|
2024
|
+
|
|
2025
|
+
const { handler } = createSubagentHandler([
|
|
2026
|
+
{
|
|
2027
|
+
agentName: "researcher",
|
|
2028
|
+
description: "Researches topics",
|
|
2029
|
+
workflow: mockWorkflow("researcherWorkflow"),
|
|
2030
|
+
},
|
|
2031
|
+
]);
|
|
2032
|
+
|
|
2033
|
+
await expect(
|
|
2034
|
+
handler(
|
|
2035
|
+
{ subagent: "researcher", description: "test", prompt: "hi" },
|
|
2036
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
2037
|
+
)
|
|
2038
|
+
).rejects.toThrow("Child Workflow execution failed: timeout");
|
|
2039
|
+
});
|
|
2040
|
+
|
|
2041
|
+
it("applies a default workflowRunTimeout when workflowOptions is omitted", async () => {
|
|
2042
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
2043
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
2044
|
+
|
|
2045
|
+
const { DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT } = await import("./handler");
|
|
2046
|
+
|
|
2047
|
+
const { handler } = createSubagentHandler([
|
|
2048
|
+
{
|
|
2049
|
+
agentName: "researcher",
|
|
2050
|
+
description: "Researches topics",
|
|
2051
|
+
workflow: mockWorkflow("researcherWorkflow"),
|
|
2052
|
+
},
|
|
2053
|
+
]);
|
|
2054
|
+
|
|
2055
|
+
await handler(
|
|
2056
|
+
{ subagent: "researcher", description: "test", prompt: "hi" },
|
|
2057
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
2058
|
+
);
|
|
2059
|
+
|
|
2060
|
+
const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
|
|
2061
|
+
if (!lastCall) throw new Error("expected executeChild call");
|
|
2062
|
+
expect(lastCall[1].workflowRunTimeout).toBe(
|
|
2063
|
+
DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT
|
|
2064
|
+
);
|
|
2065
|
+
});
|
|
2066
|
+
|
|
2067
|
+
it("lets workflowOptions.workflowRunTimeout override the default", async () => {
|
|
2068
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
2069
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
2070
|
+
|
|
2071
|
+
const { handler } = createSubagentHandler([
|
|
2072
|
+
{
|
|
2073
|
+
agentName: "researcher",
|
|
2074
|
+
description: "Researches topics",
|
|
2075
|
+
workflow: mockWorkflow("researcherWorkflow"),
|
|
2076
|
+
workflowOptions: { workflowRunTimeout: "30s" },
|
|
2077
|
+
},
|
|
2078
|
+
]);
|
|
2079
|
+
|
|
2080
|
+
await handler(
|
|
2081
|
+
{ subagent: "researcher", description: "test", prompt: "hi" },
|
|
2082
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
2083
|
+
);
|
|
2084
|
+
|
|
2085
|
+
const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
|
|
2086
|
+
if (!lastCall) throw new Error("expected executeChild call");
|
|
2087
|
+
expect(lastCall[1].workflowRunTimeout).toBe("30s");
|
|
2088
|
+
});
|
|
2089
|
+
|
|
2090
|
+
it("forwards workflowOptions to executeChild", async () => {
|
|
2091
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
2092
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
2093
|
+
|
|
2094
|
+
const { handler } = createSubagentHandler([
|
|
2095
|
+
{
|
|
2096
|
+
agentName: "researcher",
|
|
2097
|
+
description: "Researches topics",
|
|
2098
|
+
workflow: mockWorkflow("researcherWorkflow"),
|
|
2099
|
+
workflowOptions: {
|
|
2100
|
+
workflowRunTimeout: "5m",
|
|
2101
|
+
workflowTaskTimeout: "30s",
|
|
2102
|
+
retry: { maximumAttempts: 1 },
|
|
2103
|
+
},
|
|
2104
|
+
},
|
|
2105
|
+
]);
|
|
2106
|
+
|
|
2107
|
+
await handler(
|
|
2108
|
+
{ subagent: "researcher", description: "test", prompt: "hi" },
|
|
2109
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
2110
|
+
);
|
|
2111
|
+
|
|
2112
|
+
const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
|
|
2113
|
+
if (!lastCall) throw new Error("expected executeChild call");
|
|
2114
|
+
expect(lastCall[1]).toMatchObject({
|
|
2115
|
+
workflowRunTimeout: "5m",
|
|
2116
|
+
workflowTaskTimeout: "30s",
|
|
2117
|
+
retry: { maximumAttempts: 1 },
|
|
2118
|
+
});
|
|
2119
|
+
});
|
|
2120
|
+
|
|
2121
|
+
it("does not let workflowOptions override workflowId, taskQueue, or args", async () => {
|
|
2122
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
2123
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
2124
|
+
|
|
2125
|
+
const { handler } = createSubagentHandler([
|
|
2126
|
+
{
|
|
2127
|
+
agentName: "researcher",
|
|
2128
|
+
description: "Researches topics",
|
|
2129
|
+
workflow: mockWorkflow("researcherWorkflow"),
|
|
2130
|
+
taskQueue: "my-queue",
|
|
2131
|
+
workflowOptions: {
|
|
2132
|
+
// Intentionally violates the public Omit<> type to prove the
|
|
2133
|
+
// handler still wins at runtime. Cast removes the type error.
|
|
2134
|
+
...({
|
|
2135
|
+
workflowId: "forbidden-id",
|
|
2136
|
+
taskQueue: "forbidden-queue",
|
|
2137
|
+
args: ["forbidden"],
|
|
2138
|
+
} as Record<string, unknown>),
|
|
2139
|
+
},
|
|
2140
|
+
},
|
|
2141
|
+
]);
|
|
2142
|
+
|
|
2143
|
+
await handler(
|
|
2144
|
+
{ subagent: "researcher", description: "test", prompt: "hello" },
|
|
2145
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
2146
|
+
);
|
|
2147
|
+
|
|
2148
|
+
const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
|
|
2149
|
+
if (!lastCall) throw new Error("expected executeChild call");
|
|
2150
|
+
expect(lastCall[1].workflowId).not.toBe("forbidden-id");
|
|
2151
|
+
expect(lastCall[1].workflowId).toMatch(/^researcher-/);
|
|
2152
|
+
expect(lastCall[1].taskQueue).toBe("my-queue");
|
|
2153
|
+
expect(lastCall[1].args[0]).toBe("hello");
|
|
2154
|
+
});
|
|
2155
|
+
|
|
2156
|
+
it("clears lazy-creator bookkeeping on failure so the next call can re-try", async () => {
|
|
2157
|
+
const opsMock = makeMockSandboxOps();
|
|
2158
|
+
const { executeChild } = await import("@temporalio/workflow");
|
|
2159
|
+
const execMock = executeChild as ReturnType<typeof vi.fn>;
|
|
2160
|
+
|
|
2161
|
+
const lazySubagent: SubagentConfig = {
|
|
2162
|
+
agentName: "lazy",
|
|
2163
|
+
description: "Lazy sandbox init",
|
|
2164
|
+
workflow: mockWorkflow(),
|
|
2165
|
+
sandbox: {
|
|
2166
|
+
source: "own",
|
|
2167
|
+
init: "once",
|
|
2168
|
+
continuation: "fork",
|
|
2169
|
+
proxy: () => opsMock,
|
|
2170
|
+
},
|
|
2171
|
+
};
|
|
2172
|
+
|
|
2173
|
+
const { handler } = createSubagentHandler([lazySubagent]);
|
|
2174
|
+
|
|
2175
|
+
execMock.mockImplementationOnce(async () => {
|
|
2176
|
+
throw new Error("init failed");
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
await expect(
|
|
2180
|
+
handler(
|
|
2181
|
+
{ subagent: "lazy", description: "test", prompt: "first" },
|
|
2182
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
2183
|
+
)
|
|
2184
|
+
).rejects.toThrow("init failed");
|
|
2185
|
+
|
|
2186
|
+
// A second call must be able to take the creator role again (no stranded
|
|
2187
|
+
// "creating" flag) and succeed.
|
|
2188
|
+
nextStartChildResult = () => ({
|
|
2189
|
+
toolResponse: "ok",
|
|
2190
|
+
data: null,
|
|
2191
|
+
threadId: "child-t-2",
|
|
2192
|
+
sandboxId: "child-sb-2",
|
|
2193
|
+
});
|
|
2194
|
+
|
|
2195
|
+
const result = await handler(
|
|
2196
|
+
{ subagent: "lazy", description: "test", prompt: "second" },
|
|
2197
|
+
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
2198
|
+
);
|
|
2199
|
+
|
|
2200
|
+
expect(result.toolResponse).toBe("ok");
|
|
2201
|
+
});
|
|
1850
2202
|
});
|
|
1851
2203
|
|
|
1852
2204
|
// ---------------------------------------------------------------------------
|
|
@@ -2054,6 +2406,7 @@ describe("defineSubagentWorkflow", () => {
|
|
|
2054
2406
|
sandboxShutdown: "destroy",
|
|
2055
2407
|
thread: { mode: "fork", threadId: "prev-42" },
|
|
2056
2408
|
onSandboxReady: expect.any(Function),
|
|
2409
|
+
onSessionExit: expect.any(Function),
|
|
2057
2410
|
});
|
|
2058
2411
|
});
|
|
2059
2412
|
|
|
@@ -2073,6 +2426,7 @@ describe("defineSubagentWorkflow", () => {
|
|
|
2073
2426
|
sandboxShutdown: "destroy",
|
|
2074
2427
|
sandbox: { mode: "inherit", sandboxId: "sb-123" },
|
|
2075
2428
|
onSandboxReady: expect.any(Function),
|
|
2429
|
+
onSessionExit: expect.any(Function),
|
|
2076
2430
|
});
|
|
2077
2431
|
});
|
|
2078
2432
|
|
|
@@ -2092,6 +2446,7 @@ describe("defineSubagentWorkflow", () => {
|
|
|
2092
2446
|
sandboxShutdown: "destroy",
|
|
2093
2447
|
sandbox: { mode: "fork", sandboxId: "prev-sb-1" },
|
|
2094
2448
|
onSandboxReady: expect.any(Function),
|
|
2449
|
+
onSessionExit: expect.any(Function),
|
|
2095
2450
|
});
|
|
2096
2451
|
});
|
|
2097
2452
|
|
|
@@ -2115,6 +2470,7 @@ describe("defineSubagentWorkflow", () => {
|
|
|
2115
2470
|
thread: { mode: "fork", threadId: "prev-t" },
|
|
2116
2471
|
sandbox: { mode: "fork", sandboxId: "prev-sb" },
|
|
2117
2472
|
onSandboxReady: expect.any(Function),
|
|
2473
|
+
onSessionExit: expect.any(Function),
|
|
2118
2474
|
});
|
|
2119
2475
|
});
|
|
2120
2476
|
|
|
@@ -2185,6 +2541,7 @@ describe("defineSubagentWorkflow", () => {
|
|
|
2185
2541
|
agentName: "test",
|
|
2186
2542
|
sandboxShutdown: "destroy",
|
|
2187
2543
|
onSandboxReady: expect.any(Function),
|
|
2544
|
+
onSessionExit: expect.any(Function),
|
|
2188
2545
|
});
|
|
2189
2546
|
});
|
|
2190
2547
|
|
|
@@ -2207,4 +2564,171 @@ describe("defineSubagentWorkflow", () => {
|
|
|
2207
2564
|
|
|
2208
2565
|
expect(capturedSession?.sandboxShutdown).toBe("keep-until-parent-close");
|
|
2209
2566
|
});
|
|
2567
|
+
|
|
2568
|
+
// -------------------------------------------------------------------------
|
|
2569
|
+
// Auto-forwarding of session outputs + signal payload
|
|
2570
|
+
// -------------------------------------------------------------------------
|
|
2571
|
+
|
|
2572
|
+
it("auto-forwards baseSnapshot captured via onSandboxReady", async () => {
|
|
2573
|
+
const baseSnapshot = {
|
|
2574
|
+
sandboxId: "sb-1",
|
|
2575
|
+
providerId: "test",
|
|
2576
|
+
data: { tag: "base" },
|
|
2577
|
+
createdAt: new Date().toISOString(),
|
|
2578
|
+
};
|
|
2579
|
+
const workflow = defineSubagentWorkflow(
|
|
2580
|
+
{ name: "test", description: "test agent" },
|
|
2581
|
+
async (_prompt, sessionInput) => {
|
|
2582
|
+
sessionInput.onSandboxReady?.({ sandboxId: "sb-1", baseSnapshot });
|
|
2583
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
2584
|
+
}
|
|
2585
|
+
);
|
|
2586
|
+
|
|
2587
|
+
const result = await workflow("go", {});
|
|
2588
|
+
expect(result.baseSnapshot).toEqual(baseSnapshot);
|
|
2589
|
+
});
|
|
2590
|
+
|
|
2591
|
+
it("auto-forwards sandboxId and snapshot captured via onSessionExit", async () => {
|
|
2592
|
+
const snapshot = {
|
|
2593
|
+
sandboxId: "sb-1",
|
|
2594
|
+
providerId: "test",
|
|
2595
|
+
data: { tag: "exit" },
|
|
2596
|
+
createdAt: new Date().toISOString(),
|
|
2597
|
+
};
|
|
2598
|
+
const workflow = defineSubagentWorkflow(
|
|
2599
|
+
{ name: "test", description: "test agent" },
|
|
2600
|
+
async (_prompt, sessionInput) => {
|
|
2601
|
+
sessionInput.onSessionExit?.({
|
|
2602
|
+
sandboxId: "sb-1",
|
|
2603
|
+
snapshot,
|
|
2604
|
+
threadId: "t",
|
|
2605
|
+
});
|
|
2606
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
2607
|
+
}
|
|
2608
|
+
);
|
|
2609
|
+
|
|
2610
|
+
const result = await workflow("go", {});
|
|
2611
|
+
expect(result.sandboxId).toBe("sb-1");
|
|
2612
|
+
expect(result.snapshot).toEqual(snapshot);
|
|
2613
|
+
});
|
|
2614
|
+
|
|
2615
|
+
it("fn-explicit sandbox outputs win over captured session outputs", async () => {
|
|
2616
|
+
const workflow = defineSubagentWorkflow(
|
|
2617
|
+
{ name: "test", description: "test agent" },
|
|
2618
|
+
async (_prompt, sessionInput) => {
|
|
2619
|
+
sessionInput.onSessionExit?.({
|
|
2620
|
+
sandboxId: "session-sb",
|
|
2621
|
+
snapshot: {
|
|
2622
|
+
sandboxId: "session-sb",
|
|
2623
|
+
providerId: "test",
|
|
2624
|
+
data: { tag: "session" },
|
|
2625
|
+
createdAt: new Date().toISOString(),
|
|
2626
|
+
},
|
|
2627
|
+
threadId: "t",
|
|
2628
|
+
});
|
|
2629
|
+
return {
|
|
2630
|
+
toolResponse: "ok",
|
|
2631
|
+
data: null,
|
|
2632
|
+
threadId: "t",
|
|
2633
|
+
sandboxId: "explicit-sb",
|
|
2634
|
+
snapshot: {
|
|
2635
|
+
sandboxId: "explicit-sb",
|
|
2636
|
+
providerId: "test",
|
|
2637
|
+
data: { tag: "explicit" },
|
|
2638
|
+
createdAt: new Date().toISOString(),
|
|
2639
|
+
},
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
);
|
|
2643
|
+
|
|
2644
|
+
const result = await workflow("go", {});
|
|
2645
|
+
expect(result.sandboxId).toBe("explicit-sb");
|
|
2646
|
+
expect(
|
|
2647
|
+
(result.snapshot as { data: { tag: string } } | undefined)?.data
|
|
2648
|
+
).toEqual({ tag: "explicit" });
|
|
2649
|
+
});
|
|
2650
|
+
|
|
2651
|
+
it("signals parent with baseSnapshot via childSandboxReadySignal", async () => {
|
|
2652
|
+
const { getExternalWorkflowHandle } = await import("@temporalio/workflow");
|
|
2653
|
+
const ghMock = getExternalWorkflowHandle as ReturnType<typeof vi.fn>;
|
|
2654
|
+
const baseSnapshot = {
|
|
2655
|
+
sandboxId: "sb-1",
|
|
2656
|
+
providerId: "test",
|
|
2657
|
+
data: { tag: "base" },
|
|
2658
|
+
createdAt: new Date().toISOString(),
|
|
2659
|
+
};
|
|
2660
|
+
|
|
2661
|
+
const workflow = defineSubagentWorkflow(
|
|
2662
|
+
{ name: "test", description: "test agent" },
|
|
2663
|
+
async (_prompt, sessionInput) => {
|
|
2664
|
+
sessionInput.onSandboxReady?.({ sandboxId: "sb-1", baseSnapshot });
|
|
2665
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
2666
|
+
}
|
|
2667
|
+
);
|
|
2668
|
+
|
|
2669
|
+
await workflow("go", {});
|
|
2670
|
+
|
|
2671
|
+
const handle = ghMock.mock.results.at(-1)?.value as {
|
|
2672
|
+
signal: ReturnType<typeof vi.fn>;
|
|
2673
|
+
};
|
|
2674
|
+
expect(handle.signal).toHaveBeenCalledWith(
|
|
2675
|
+
expect.objectContaining({ name: "childSandboxReady" }),
|
|
2676
|
+
expect.objectContaining({
|
|
2677
|
+
childWorkflowId: "child-wf-1",
|
|
2678
|
+
sandboxId: "sb-1",
|
|
2679
|
+
baseSnapshot,
|
|
2680
|
+
})
|
|
2681
|
+
);
|
|
2682
|
+
});
|
|
2683
|
+
|
|
2684
|
+
it("omits baseSnapshot from signal when session did not capture one", async () => {
|
|
2685
|
+
const { getExternalWorkflowHandle } = await import("@temporalio/workflow");
|
|
2686
|
+
const ghMock = getExternalWorkflowHandle as ReturnType<typeof vi.fn>;
|
|
2687
|
+
|
|
2688
|
+
const workflow = defineSubagentWorkflow(
|
|
2689
|
+
{ name: "test", description: "test agent" },
|
|
2690
|
+
async (_prompt, sessionInput) => {
|
|
2691
|
+
sessionInput.onSandboxReady?.({ sandboxId: "sb-1" });
|
|
2692
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
2693
|
+
}
|
|
2694
|
+
);
|
|
2695
|
+
|
|
2696
|
+
await workflow("go", {});
|
|
2697
|
+
|
|
2698
|
+
const handle = ghMock.mock.results.at(-1)?.value as {
|
|
2699
|
+
signal: ReturnType<typeof vi.fn>;
|
|
2700
|
+
};
|
|
2701
|
+
const payload = handle.signal.mock.calls.at(-1)?.[1] as {
|
|
2702
|
+
childWorkflowId: string;
|
|
2703
|
+
sandboxId: string;
|
|
2704
|
+
baseSnapshot?: unknown;
|
|
2705
|
+
};
|
|
2706
|
+
expect(payload).toEqual({
|
|
2707
|
+
childWorkflowId: "child-wf-1",
|
|
2708
|
+
sandboxId: "sb-1",
|
|
2709
|
+
});
|
|
2710
|
+
expect(payload.baseSnapshot).toBeUndefined();
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2713
|
+
it("skips the signal when the sandbox is reused (continue mode)", async () => {
|
|
2714
|
+
const { getExternalWorkflowHandle } = await import("@temporalio/workflow");
|
|
2715
|
+
const ghMock = getExternalWorkflowHandle as ReturnType<typeof vi.fn>;
|
|
2716
|
+
|
|
2717
|
+
const workflow = defineSubagentWorkflow(
|
|
2718
|
+
{ name: "test", description: "test agent" },
|
|
2719
|
+
async (_prompt, sessionInput) => {
|
|
2720
|
+
sessionInput.onSandboxReady?.({ sandboxId: "sb-1" });
|
|
2721
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
2722
|
+
}
|
|
2723
|
+
);
|
|
2724
|
+
|
|
2725
|
+
await workflow("go", {
|
|
2726
|
+
sandbox: { mode: "continue", sandboxId: "sb-1" },
|
|
2727
|
+
});
|
|
2728
|
+
|
|
2729
|
+
const handle = ghMock.mock.results.at(-1)?.value as {
|
|
2730
|
+
signal: ReturnType<typeof vi.fn>;
|
|
2731
|
+
};
|
|
2732
|
+
expect(handle.signal).not.toHaveBeenCalled();
|
|
2733
|
+
});
|
|
2210
2734
|
});
|