zeitlich 0.2.21 → 0.2.23
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 +303 -105
- package/dist/adapters/sandbox/daytona/index.cjs +7 -1
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +3 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +3 -1
- package/dist/adapters/sandbox/daytona/index.js +7 -1
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +33 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/daytona/workflow.d.cts +27 -0
- package/dist/adapters/sandbox/daytona/workflow.d.ts +27 -0
- package/dist/adapters/sandbox/daytona/workflow.js +31 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +18 -1
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +4 -2
- package/dist/adapters/sandbox/inmemory/index.d.ts +4 -2
- package/dist/adapters/sandbox/inmemory/index.js +18 -1
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +33 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +25 -0
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +25 -0
- package/dist/adapters/sandbox/inmemory/workflow.js +31 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +36 -9
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +8 -5
- package/dist/adapters/sandbox/virtual/index.d.ts +8 -5
- package/dist/adapters/sandbox/virtual/index.js +36 -9
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +33 -0
- package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -0
- package/dist/adapters/sandbox/virtual/workflow.d.cts +27 -0
- package/dist/adapters/sandbox/virtual/workflow.d.ts +27 -0
- package/dist/adapters/sandbox/virtual/workflow.js +31 -0
- package/dist/adapters/sandbox/virtual/workflow.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +9 -1
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +31 -19
- package/dist/adapters/thread/google-genai/index.d.ts +31 -19
- package/dist/adapters/thread/google-genai/index.js +9 -1
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +33 -0
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -0
- package/dist/adapters/thread/google-genai/workflow.d.cts +32 -0
- package/dist/adapters/thread/google-genai/workflow.d.ts +32 -0
- package/dist/adapters/thread/google-genai/workflow.js +31 -0
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -0
- package/dist/adapters/thread/langchain/index.cjs +9 -1
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +27 -16
- package/dist/adapters/thread/langchain/index.d.ts +27 -16
- package/dist/adapters/thread/langchain/index.js +9 -1
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +33 -0
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -0
- package/dist/adapters/thread/langchain/workflow.d.cts +32 -0
- package/dist/adapters/thread/langchain/workflow.d.ts +32 -0
- package/dist/adapters/thread/langchain/workflow.js +31 -0
- package/dist/adapters/thread/langchain/workflow.js.map +1 -0
- package/dist/index.cjs +282 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -16
- package/dist/index.d.ts +38 -16
- package/dist/index.js +281 -87
- package/dist/index.js.map +1 -1
- package/dist/queries-DModcWRy.d.cts +44 -0
- package/dist/queries-byD0jr1Y.d.ts +44 -0
- package/dist/{types-BkAYmc96.d.ts → types-B50pBPEV.d.ts} +190 -38
- package/dist/{types-YbL7JpEA.d.cts → types-Bll19FZJ.d.cts} +7 -0
- package/dist/{types-YbL7JpEA.d.ts → types-Bll19FZJ.d.ts} +7 -0
- package/dist/{queries-6Avfh74U.d.ts → types-BuXdFhaZ.d.cts} +7 -48
- package/dist/{types-BMRzfELQ.d.cts → types-ChAMwU3q.d.cts} +17 -1
- package/dist/{types-BMRzfELQ.d.ts → types-ChAMwU3q.d.ts} +17 -1
- package/dist/{types-CES_30qx.d.cts → types-DQW8l7pY.d.cts} +190 -38
- package/dist/{queries-CHa2iv_I.d.cts → types-GZ76HZSj.d.ts} +7 -48
- package/dist/workflow.cjs +244 -86
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +54 -65
- package/dist/workflow.d.ts +54 -65
- package/dist/workflow.js +243 -83
- package/dist/workflow.js.map +1 -1
- package/package.json +54 -2
- package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
- package/src/adapters/sandbox/daytona/index.ts +8 -0
- package/src/adapters/sandbox/daytona/proxy.ts +56 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +147 -0
- package/src/adapters/sandbox/e2b/index.ts +164 -0
- package/src/adapters/sandbox/e2b/types.ts +23 -0
- package/src/adapters/sandbox/inmemory/index.ts +27 -3
- package/src/adapters/sandbox/inmemory/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
- package/src/adapters/sandbox/virtual/provider.ts +9 -1
- package/src/adapters/sandbox/virtual/proxy.ts +53 -0
- package/src/adapters/sandbox/virtual/types.ts +9 -4
- package/src/adapters/thread/google-genai/activities.ts +51 -17
- package/src/adapters/thread/google-genai/index.ts +1 -0
- package/src/adapters/thread/google-genai/proxy.ts +61 -0
- package/src/adapters/thread/langchain/activities.ts +47 -14
- package/src/adapters/thread/langchain/index.ts +1 -0
- package/src/adapters/thread/langchain/proxy.ts +61 -0
- package/src/lib/lifecycle.ts +57 -0
- package/src/lib/sandbox/manager.ts +52 -6
- package/src/lib/sandbox/sandbox.test.ts +12 -11
- package/src/lib/sandbox/types.ts +31 -4
- package/src/lib/session/index.ts +4 -5
- package/src/lib/session/session-edge-cases.integration.test.ts +491 -66
- package/src/lib/session/session.integration.test.ts +92 -80
- package/src/lib/session/session.ts +108 -96
- package/src/lib/session/types.ts +87 -17
- package/src/lib/subagent/define.ts +6 -5
- package/src/lib/subagent/handler.ts +148 -16
- package/src/lib/subagent/index.ts +4 -0
- package/src/lib/subagent/register.ts +10 -3
- package/src/lib/subagent/signals.ts +8 -0
- package/src/lib/subagent/subagent.integration.test.ts +893 -128
- package/src/lib/subagent/tool.ts +2 -2
- package/src/lib/subagent/types.ts +84 -21
- package/src/lib/subagent/workflow.ts +83 -12
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +4 -1
- package/src/lib/tool-router/router.integration.test.ts +141 -5
- package/src/lib/tool-router/router.ts +13 -3
- package/src/lib/tool-router/types.ts +7 -0
- package/src/lib/workflow.test.ts +104 -27
- package/src/lib/workflow.ts +37 -19
- package/src/tools/bash/bash.test.ts +16 -7
- package/src/workflow.ts +11 -14
- package/tsup.config.ts +6 -0
|
@@ -1,21 +1,79 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
1
|
+
import { describe, expect, it, vi, afterEach } from "vitest";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
|
+
let capturedSignalHandler:
|
|
5
|
+
| ((payload: { childWorkflowId: string; result: unknown }) => void)
|
|
6
|
+
| null = null;
|
|
7
|
+
|
|
8
|
+
let nextStartChildResult: ((prompt: string) => unknown) | null = null;
|
|
9
|
+
|
|
4
10
|
vi.mock("@temporalio/workflow", () => {
|
|
5
11
|
let counter = 0;
|
|
12
|
+
|
|
13
|
+
class MockApplicationFailure extends Error {
|
|
14
|
+
nonRetryable?: boolean;
|
|
15
|
+
static create({
|
|
16
|
+
message,
|
|
17
|
+
nonRetryable,
|
|
18
|
+
}: {
|
|
19
|
+
message: string;
|
|
20
|
+
nonRetryable?: boolean;
|
|
21
|
+
}) {
|
|
22
|
+
const err = new MockApplicationFailure(message);
|
|
23
|
+
err.nonRetryable = nonRetryable;
|
|
24
|
+
return err;
|
|
25
|
+
}
|
|
26
|
+
static fromError(error: unknown) {
|
|
27
|
+
const src = error instanceof Error ? error : new Error(String(error));
|
|
28
|
+
return new MockApplicationFailure(src.message);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
6
32
|
return {
|
|
7
|
-
workflowInfo: () => ({
|
|
8
|
-
|
|
9
|
-
|
|
33
|
+
workflowInfo: () => ({
|
|
34
|
+
taskQueue: "default-queue",
|
|
35
|
+
workflowId: "child-wf-1",
|
|
36
|
+
parent: { workflowId: "parent-wf-1" },
|
|
37
|
+
}),
|
|
38
|
+
defineSignal: vi.fn((_name: string) => ({ __signal: true })),
|
|
39
|
+
setHandler: vi.fn(
|
|
40
|
+
(_signal: unknown, handler: (...a: unknown[]) => void) => {
|
|
41
|
+
capturedSignalHandler = handler as typeof capturedSignalHandler;
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
condition: vi.fn(async (fn: () => boolean) => {
|
|
45
|
+
if (!fn()) throw new Error("condition predicate was not satisfied");
|
|
46
|
+
}),
|
|
47
|
+
startChild: vi.fn(
|
|
48
|
+
async (
|
|
49
|
+
_workflow: unknown,
|
|
50
|
+
opts: { workflowId: string; args: unknown[] }
|
|
51
|
+
) => {
|
|
10
52
|
const prompt = (opts.args as [string])[0];
|
|
53
|
+
const result = nextStartChildResult
|
|
54
|
+
? nextStartChildResult(prompt)
|
|
55
|
+
: {
|
|
56
|
+
toolResponse: `Response to: ${prompt}`,
|
|
57
|
+
data: { result: "child-data" },
|
|
58
|
+
threadId: "child-thread-1",
|
|
59
|
+
usage: { inputTokens: 100, outputTokens: 50 },
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (capturedSignalHandler) {
|
|
63
|
+
capturedSignalHandler({ childWorkflowId: opts.workflowId, result });
|
|
64
|
+
}
|
|
65
|
+
|
|
11
66
|
return {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
usage: { inputTokens: 100, outputTokens: 50 },
|
|
67
|
+
signal: vi.fn(),
|
|
68
|
+
result: () => Promise.resolve(result),
|
|
69
|
+
workflowId: opts.workflowId,
|
|
16
70
|
};
|
|
17
71
|
}
|
|
18
72
|
),
|
|
73
|
+
getExternalWorkflowHandle: vi.fn((_id: string) => ({
|
|
74
|
+
signal: vi.fn(),
|
|
75
|
+
})),
|
|
76
|
+
ApplicationFailure: MockApplicationFailure,
|
|
19
77
|
uuid4: () => {
|
|
20
78
|
counter++;
|
|
21
79
|
const bytes = Array.from({ length: 16 }, (_, i) =>
|
|
@@ -34,8 +92,23 @@ import { defineSubagent } from "./define";
|
|
|
34
92
|
import type {
|
|
35
93
|
SubagentConfig,
|
|
36
94
|
SubagentSessionInput,
|
|
95
|
+
SubagentWorkflow,
|
|
37
96
|
SubagentWorkflowInput,
|
|
38
97
|
} from "./types";
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
nextStartChildResult = null;
|
|
100
|
+
capturedSignalHandler = null;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
function mockWorkflow(name?: string): SubagentWorkflow {
|
|
104
|
+
const fn = async () => ({
|
|
105
|
+
toolResponse: "ok",
|
|
106
|
+
data: null,
|
|
107
|
+
threadId: "t-1",
|
|
108
|
+
});
|
|
109
|
+
if (name) Object.defineProperty(fn, "name", { value: name });
|
|
110
|
+
return fn as SubagentWorkflow;
|
|
111
|
+
}
|
|
39
112
|
|
|
40
113
|
// ---------------------------------------------------------------------------
|
|
41
114
|
// createSubagentTool
|
|
@@ -47,7 +120,7 @@ describe("createSubagentTool", () => {
|
|
|
47
120
|
{
|
|
48
121
|
agentName: "researcher",
|
|
49
122
|
description: "Researches topics",
|
|
50
|
-
workflow:
|
|
123
|
+
workflow: mockWorkflow(),
|
|
51
124
|
},
|
|
52
125
|
]);
|
|
53
126
|
|
|
@@ -68,77 +141,80 @@ describe("createSubagentTool", () => {
|
|
|
68
141
|
{
|
|
69
142
|
agentName: "researcher",
|
|
70
143
|
description: "Researches",
|
|
71
|
-
workflow:
|
|
144
|
+
workflow: mockWorkflow(),
|
|
72
145
|
},
|
|
73
146
|
{
|
|
74
147
|
agentName: "writer",
|
|
75
148
|
description: "Writes",
|
|
76
|
-
workflow:
|
|
149
|
+
workflow: mockWorkflow(),
|
|
77
150
|
},
|
|
78
151
|
]);
|
|
79
152
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
153
|
+
expect(
|
|
154
|
+
tool.schema.safeParse({
|
|
155
|
+
subagent: "researcher",
|
|
156
|
+
description: "d",
|
|
157
|
+
prompt: "p",
|
|
158
|
+
}).success
|
|
159
|
+
).toBe(true);
|
|
160
|
+
expect(
|
|
161
|
+
tool.schema.safeParse({
|
|
162
|
+
subagent: "writer",
|
|
163
|
+
description: "d",
|
|
164
|
+
prompt: "p",
|
|
165
|
+
}).success
|
|
166
|
+
).toBe(true);
|
|
167
|
+
expect(
|
|
168
|
+
tool.schema.safeParse({
|
|
169
|
+
subagent: "nonexistent",
|
|
170
|
+
description: "d",
|
|
171
|
+
prompt: "p",
|
|
172
|
+
}).success
|
|
173
|
+
).toBe(false);
|
|
100
174
|
});
|
|
101
175
|
|
|
102
|
-
it("adds threadId field when
|
|
176
|
+
it("adds threadId field when thread mode allows continuation", () => {
|
|
103
177
|
const tool = createSubagentTool([
|
|
104
178
|
{
|
|
105
179
|
agentName: "agent",
|
|
106
180
|
description: "supports continuation",
|
|
107
|
-
workflow:
|
|
108
|
-
|
|
181
|
+
workflow: mockWorkflow(),
|
|
182
|
+
thread: "fork",
|
|
109
183
|
},
|
|
110
184
|
]);
|
|
111
185
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
186
|
+
expect(
|
|
187
|
+
tool.schema.safeParse({
|
|
188
|
+
subagent: "agent",
|
|
189
|
+
description: "d",
|
|
190
|
+
prompt: "p",
|
|
191
|
+
threadId: "some-thread",
|
|
192
|
+
}).success
|
|
193
|
+
).toBe(true);
|
|
194
|
+
|
|
195
|
+
expect(
|
|
196
|
+
tool.schema.safeParse({
|
|
197
|
+
subagent: "agent",
|
|
198
|
+
description: "d",
|
|
199
|
+
prompt: "p",
|
|
200
|
+
threadId: null,
|
|
201
|
+
}).success
|
|
202
|
+
).toBe(true);
|
|
127
203
|
});
|
|
128
204
|
|
|
129
|
-
it("does not include threadId field when
|
|
205
|
+
it("does not include threadId field when thread mode is new", () => {
|
|
130
206
|
const tool = createSubagentTool([
|
|
131
207
|
{
|
|
132
208
|
agentName: "basic",
|
|
133
209
|
description: "basic agent",
|
|
134
|
-
workflow:
|
|
210
|
+
workflow: mockWorkflow(),
|
|
135
211
|
},
|
|
136
212
|
]);
|
|
137
213
|
|
|
138
214
|
const result = tool.schema.safeParse({
|
|
139
215
|
subagent: "basic",
|
|
140
|
-
description: "
|
|
141
|
-
prompt: "
|
|
216
|
+
description: "d",
|
|
217
|
+
prompt: "p",
|
|
142
218
|
threadId: "should-strip",
|
|
143
219
|
});
|
|
144
220
|
expect(result.success).toBe(true);
|
|
@@ -158,8 +234,8 @@ describe("createSubagentTool", () => {
|
|
|
158
234
|
{
|
|
159
235
|
agentName: "cont-agent",
|
|
160
236
|
description: "Supports continuation",
|
|
161
|
-
workflow:
|
|
162
|
-
|
|
237
|
+
workflow: mockWorkflow(),
|
|
238
|
+
thread: "fork",
|
|
163
239
|
},
|
|
164
240
|
]);
|
|
165
241
|
|
|
@@ -175,11 +251,11 @@ describe("createSubagentHandler", () => {
|
|
|
175
251
|
const basicSubagent: SubagentConfig = {
|
|
176
252
|
agentName: "researcher",
|
|
177
253
|
description: "Researches topics",
|
|
178
|
-
workflow: "researcherWorkflow",
|
|
254
|
+
workflow: mockWorkflow("researcherWorkflow"),
|
|
179
255
|
};
|
|
180
256
|
|
|
181
257
|
it("executes child workflow and returns response", async () => {
|
|
182
|
-
const handler = createSubagentHandler([basicSubagent]);
|
|
258
|
+
const { handler } = createSubagentHandler([basicSubagent]);
|
|
183
259
|
|
|
184
260
|
const result = await handler(
|
|
185
261
|
{ subagent: "researcher", description: "test", prompt: "Find info" },
|
|
@@ -191,7 +267,7 @@ describe("createSubagentHandler", () => {
|
|
|
191
267
|
});
|
|
192
268
|
|
|
193
269
|
it("throws for unknown subagent name", async () => {
|
|
194
|
-
const handler = createSubagentHandler([basicSubagent]);
|
|
270
|
+
const { handler } = createSubagentHandler([basicSubagent]);
|
|
195
271
|
|
|
196
272
|
await expect(
|
|
197
273
|
handler(
|
|
@@ -202,12 +278,12 @@ describe("createSubagentHandler", () => {
|
|
|
202
278
|
});
|
|
203
279
|
|
|
204
280
|
it("includes available subagent names in error message", async () => {
|
|
205
|
-
const handler = createSubagentHandler([
|
|
281
|
+
const { handler } = createSubagentHandler([
|
|
206
282
|
basicSubagent,
|
|
207
283
|
{
|
|
208
284
|
agentName: "writer",
|
|
209
285
|
description: "Writes",
|
|
210
|
-
workflow: "writerWorkflow",
|
|
286
|
+
workflow: mockWorkflow("writerWorkflow"),
|
|
211
287
|
},
|
|
212
288
|
]);
|
|
213
289
|
|
|
@@ -220,8 +296,7 @@ describe("createSubagentHandler", () => {
|
|
|
220
296
|
});
|
|
221
297
|
|
|
222
298
|
it("validates result against resultSchema", async () => {
|
|
223
|
-
|
|
224
|
-
(executeChild as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
|
299
|
+
nextStartChildResult = () => ({
|
|
225
300
|
toolResponse: "result",
|
|
226
301
|
data: { invalid: "data" },
|
|
227
302
|
threadId: "child-t",
|
|
@@ -230,11 +305,11 @@ describe("createSubagentHandler", () => {
|
|
|
230
305
|
const validatedSubagent: SubagentConfig = {
|
|
231
306
|
agentName: "validated",
|
|
232
307
|
description: "Has validation",
|
|
233
|
-
workflow:
|
|
308
|
+
workflow: mockWorkflow(),
|
|
234
309
|
resultSchema: z.object({ expected: z.string() }),
|
|
235
310
|
};
|
|
236
311
|
|
|
237
|
-
const handler = createSubagentHandler([validatedSubagent]);
|
|
312
|
+
const { handler } = createSubagentHandler([validatedSubagent]);
|
|
238
313
|
|
|
239
314
|
const result = await handler(
|
|
240
315
|
{ subagent: "validated", description: "test", prompt: "test" },
|
|
@@ -245,9 +320,8 @@ describe("createSubagentHandler", () => {
|
|
|
245
320
|
expect(result.data).toBeNull();
|
|
246
321
|
});
|
|
247
322
|
|
|
248
|
-
it("appends thread ID when
|
|
249
|
-
|
|
250
|
-
(executeChild as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
|
323
|
+
it("appends thread ID when thread is fork", async () => {
|
|
324
|
+
nextStartChildResult = () => ({
|
|
251
325
|
toolResponse: "Some response",
|
|
252
326
|
data: null,
|
|
253
327
|
threadId: "child-thread-99",
|
|
@@ -256,11 +330,11 @@ describe("createSubagentHandler", () => {
|
|
|
256
330
|
const contSubagent: SubagentConfig = {
|
|
257
331
|
agentName: "cont",
|
|
258
332
|
description: "Continues threads",
|
|
259
|
-
workflow:
|
|
260
|
-
|
|
333
|
+
workflow: mockWorkflow(),
|
|
334
|
+
thread: "fork",
|
|
261
335
|
};
|
|
262
336
|
|
|
263
|
-
const handler = createSubagentHandler([contSubagent]);
|
|
337
|
+
const { handler } = createSubagentHandler([contSubagent]);
|
|
264
338
|
|
|
265
339
|
const result = await handler(
|
|
266
340
|
{ subagent: "cont", description: "test", prompt: "test" },
|
|
@@ -271,14 +345,13 @@ describe("createSubagentHandler", () => {
|
|
|
271
345
|
});
|
|
272
346
|
|
|
273
347
|
it("returns fallback when child workflow returns no toolResponse", async () => {
|
|
274
|
-
|
|
275
|
-
(executeChild as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
|
|
348
|
+
nextStartChildResult = () => ({
|
|
276
349
|
toolResponse: null,
|
|
277
350
|
data: null,
|
|
278
351
|
threadId: "child-t",
|
|
279
352
|
});
|
|
280
353
|
|
|
281
|
-
const handler = createSubagentHandler([basicSubagent]);
|
|
354
|
+
const { handler } = createSubagentHandler([basicSubagent]);
|
|
282
355
|
|
|
283
356
|
const result = await handler(
|
|
284
357
|
{ subagent: "researcher", description: "test", prompt: "test" },
|
|
@@ -289,23 +362,18 @@ describe("createSubagentHandler", () => {
|
|
|
289
362
|
expect(result.data).toBeNull();
|
|
290
363
|
});
|
|
291
364
|
|
|
292
|
-
it("passes
|
|
293
|
-
const {
|
|
294
|
-
const
|
|
295
|
-
execMock.mockResolvedValueOnce({
|
|
296
|
-
toolResponse: "ok",
|
|
297
|
-
data: null,
|
|
298
|
-
threadId: "child-t",
|
|
299
|
-
});
|
|
365
|
+
it("passes sandbox inherit to child when sandbox is inherit", async () => {
|
|
366
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
367
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
300
368
|
|
|
301
369
|
const inheritSubagent: SubagentConfig = {
|
|
302
370
|
agentName: "inherit-agent",
|
|
303
371
|
description: "Inherits sandbox",
|
|
304
|
-
workflow:
|
|
372
|
+
workflow: mockWorkflow(),
|
|
305
373
|
sandbox: "inherit",
|
|
306
374
|
};
|
|
307
375
|
|
|
308
|
-
const handler = createSubagentHandler([inheritSubagent]);
|
|
376
|
+
const { handler } = createSubagentHandler([inheritSubagent]);
|
|
309
377
|
|
|
310
378
|
await handler(
|
|
311
379
|
{ subagent: "inherit-agent", description: "test", prompt: "test" },
|
|
@@ -317,32 +385,505 @@ describe("createSubagentHandler", () => {
|
|
|
317
385
|
}
|
|
318
386
|
);
|
|
319
387
|
|
|
320
|
-
const lastCall =
|
|
321
|
-
if (!lastCall) throw new Error("expected
|
|
388
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
389
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
390
|
+
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
391
|
+
expect(workflowInput.sandbox).toEqual({
|
|
392
|
+
mode: "inherit",
|
|
393
|
+
sandboxId: "parent-sb",
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it("throws when sandbox is inherit but parent has no sandbox", async () => {
|
|
398
|
+
const inheritSubagent: SubagentConfig = {
|
|
399
|
+
agentName: "inherit-agent",
|
|
400
|
+
description: "Inherits sandbox",
|
|
401
|
+
workflow: mockWorkflow(),
|
|
402
|
+
sandbox: "inherit",
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const { handler } = createSubagentHandler([inheritSubagent]);
|
|
406
|
+
|
|
407
|
+
await expect(
|
|
408
|
+
handler(
|
|
409
|
+
{ subagent: "inherit-agent", description: "test", prompt: "test" },
|
|
410
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
411
|
+
)
|
|
412
|
+
).rejects.toThrow(
|
|
413
|
+
'sandbox: "inherit" but the parent has no sandbox'
|
|
414
|
+
);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("does not pass sandboxId to child when sandbox is own", async () => {
|
|
418
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
419
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
420
|
+
|
|
421
|
+
const ownSubagent: SubagentConfig = {
|
|
422
|
+
agentName: "own-agent",
|
|
423
|
+
description: "Own sandbox",
|
|
424
|
+
workflow: mockWorkflow(),
|
|
425
|
+
sandbox: "own",
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const { handler } = createSubagentHandler([ownSubagent]);
|
|
429
|
+
|
|
430
|
+
await handler(
|
|
431
|
+
{ subagent: "own-agent", description: "test", prompt: "test" },
|
|
432
|
+
{
|
|
433
|
+
threadId: "t",
|
|
434
|
+
toolCallId: "tc",
|
|
435
|
+
toolName: "Subagent",
|
|
436
|
+
sandboxId: "parent-sb",
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
441
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
322
442
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
323
|
-
expect(workflowInput.
|
|
443
|
+
expect(workflowInput.sandbox).toBeUndefined();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("resolves context function at invocation time", async () => {
|
|
447
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
448
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
449
|
+
|
|
450
|
+
let counter = 0;
|
|
451
|
+
const dynamicSubagent: SubagentConfig = {
|
|
452
|
+
agentName: "dynamic-ctx",
|
|
453
|
+
description: "Dynamic context",
|
|
454
|
+
workflow: mockWorkflow(),
|
|
455
|
+
context: () => {
|
|
456
|
+
counter++;
|
|
457
|
+
return { invocation: counter };
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const { handler } = createSubagentHandler([dynamicSubagent]);
|
|
462
|
+
|
|
463
|
+
await handler(
|
|
464
|
+
{ subagent: "dynamic-ctx", description: "test", prompt: "test" },
|
|
465
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
469
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
470
|
+
const context = lastCall[1].args[2] as Record<string, unknown>;
|
|
471
|
+
expect(context).toEqual({ invocation: 1 });
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it("passes static context unchanged", async () => {
|
|
475
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
476
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
477
|
+
|
|
478
|
+
const staticSubagent: SubagentConfig = {
|
|
479
|
+
agentName: "static-ctx",
|
|
480
|
+
description: "Static context",
|
|
481
|
+
workflow: mockWorkflow(),
|
|
482
|
+
context: { key: "value" },
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const { handler } = createSubagentHandler([staticSubagent]);
|
|
486
|
+
|
|
487
|
+
await handler(
|
|
488
|
+
{ subagent: "static-ctx", description: "test", prompt: "test" },
|
|
489
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
493
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
494
|
+
const context = lastCall[1].args[2] as Record<string, unknown>;
|
|
495
|
+
expect(context).toEqual({ key: "value" });
|
|
324
496
|
});
|
|
325
497
|
|
|
326
498
|
it("does not pass sandboxId when sandbox is own", async () => {
|
|
327
|
-
const {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
499
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
500
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
501
|
+
|
|
502
|
+
const ownSubagent: SubagentConfig = {
|
|
503
|
+
agentName: "own-agent",
|
|
504
|
+
description: "Own sandbox",
|
|
505
|
+
workflow: mockWorkflow(),
|
|
506
|
+
sandbox: "own",
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const { handler } = createSubagentHandler([ownSubagent]);
|
|
510
|
+
|
|
511
|
+
await handler(
|
|
512
|
+
{ subagent: "own-agent", description: "test", prompt: "test" },
|
|
513
|
+
{
|
|
514
|
+
threadId: "t",
|
|
515
|
+
toolCallId: "tc",
|
|
516
|
+
toolName: "Subagent",
|
|
517
|
+
sandboxId: "parent-sb",
|
|
518
|
+
}
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
522
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
523
|
+
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
524
|
+
expect(workflowInput.sandbox).toBeUndefined();
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// --- Thread mode ---
|
|
528
|
+
|
|
529
|
+
it("passes thread fork when thread is fork and threadId provided", async () => {
|
|
530
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
531
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
532
|
+
|
|
533
|
+
const contSubagent: SubagentConfig = {
|
|
534
|
+
agentName: "cont",
|
|
535
|
+
description: "Continues",
|
|
536
|
+
workflow: mockWorkflow(),
|
|
537
|
+
thread: "fork",
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
const { handler } = createSubagentHandler([contSubagent]);
|
|
541
|
+
|
|
542
|
+
await handler(
|
|
543
|
+
{
|
|
544
|
+
subagent: "cont",
|
|
545
|
+
description: "test",
|
|
546
|
+
prompt: "test",
|
|
547
|
+
threadId: "prev-thread-42",
|
|
548
|
+
},
|
|
549
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
553
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
554
|
+
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
555
|
+
expect(workflowInput.thread).toEqual({
|
|
556
|
+
mode: "fork",
|
|
557
|
+
threadId: "prev-thread-42",
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it("passes thread continue when thread is continue", async () => {
|
|
562
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
563
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
564
|
+
|
|
565
|
+
const contSubagent: SubagentConfig = {
|
|
566
|
+
agentName: "cont-mode",
|
|
567
|
+
description: "Continue mode",
|
|
568
|
+
workflow: mockWorkflow(),
|
|
569
|
+
thread: "continue",
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const { handler } = createSubagentHandler([contSubagent]);
|
|
573
|
+
|
|
574
|
+
await handler(
|
|
575
|
+
{
|
|
576
|
+
subagent: "cont-mode",
|
|
577
|
+
description: "test",
|
|
578
|
+
prompt: "test",
|
|
579
|
+
threadId: "prev-thread-99",
|
|
580
|
+
},
|
|
581
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
585
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
586
|
+
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
587
|
+
expect(workflowInput.thread).toEqual({
|
|
588
|
+
mode: "continue",
|
|
589
|
+
threadId: "prev-thread-99",
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("does not pass thread when thread is new", async () => {
|
|
594
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
595
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
596
|
+
|
|
597
|
+
const noContSubagent: SubagentConfig = {
|
|
598
|
+
agentName: "no-cont",
|
|
599
|
+
description: "No continuation",
|
|
600
|
+
workflow: mockWorkflow(),
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const { handler } = createSubagentHandler([noContSubagent]);
|
|
604
|
+
|
|
605
|
+
await handler(
|
|
606
|
+
{
|
|
607
|
+
subagent: "no-cont",
|
|
608
|
+
description: "test",
|
|
609
|
+
prompt: "test",
|
|
610
|
+
threadId: "prev-thread",
|
|
611
|
+
},
|
|
612
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
616
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
617
|
+
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
618
|
+
expect(workflowInput.thread).toBeUndefined();
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// --- Sandbox continuation ---
|
|
622
|
+
|
|
623
|
+
it("does not pass sandbox when thread is fork (own sandbox)", async () => {
|
|
624
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
625
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
626
|
+
|
|
627
|
+
const contSandboxSubagent: SubagentConfig = {
|
|
628
|
+
agentName: "sb-cont",
|
|
629
|
+
description: "Sandbox continuation",
|
|
630
|
+
workflow: mockWorkflow(),
|
|
631
|
+
thread: "fork",
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
const { handler } = createSubagentHandler([contSandboxSubagent]);
|
|
635
|
+
|
|
636
|
+
await handler(
|
|
637
|
+
{ subagent: "sb-cont", description: "test", prompt: "first run" },
|
|
638
|
+
{
|
|
639
|
+
threadId: "t",
|
|
640
|
+
toolCallId: "tc",
|
|
641
|
+
toolName: "Subagent",
|
|
642
|
+
sandboxId: "parent-sb",
|
|
643
|
+
}
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
647
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
648
|
+
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
649
|
+
expect(workflowInput.sandbox).toBeUndefined();
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it("tracks sandbox ID and passes sandbox fork on continuation", async () => {
|
|
653
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
654
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
655
|
+
|
|
656
|
+
nextStartChildResult = () => ({
|
|
657
|
+
toolResponse: "first run done",
|
|
331
658
|
data: null,
|
|
332
|
-
threadId: "child-
|
|
659
|
+
threadId: "child-thread-A",
|
|
660
|
+
sandboxId: "child-sb-1",
|
|
333
661
|
});
|
|
334
662
|
|
|
663
|
+
const contSandboxSubagent: SubagentConfig = {
|
|
664
|
+
agentName: "sb-cont",
|
|
665
|
+
description: "Sandbox continuation",
|
|
666
|
+
workflow: mockWorkflow(),
|
|
667
|
+
thread: "fork",
|
|
668
|
+
sandbox: "own",
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const { handler } = createSubagentHandler([contSandboxSubagent]);
|
|
672
|
+
|
|
673
|
+
await handler(
|
|
674
|
+
{ subagent: "sb-cont", description: "test", prompt: "first" },
|
|
675
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
nextStartChildResult = () => ({
|
|
679
|
+
toolResponse: "second run done",
|
|
680
|
+
data: null,
|
|
681
|
+
threadId: "child-thread-B",
|
|
682
|
+
sandboxId: "child-sb-2",
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
await handler(
|
|
686
|
+
{
|
|
687
|
+
subagent: "sb-cont",
|
|
688
|
+
description: "test",
|
|
689
|
+
prompt: "second",
|
|
690
|
+
threadId: "child-thread-A",
|
|
691
|
+
},
|
|
692
|
+
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
const secondCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
696
|
+
if (!secondCall) throw new Error("expected second startChild call");
|
|
697
|
+
const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
698
|
+
expect(workflowInput.thread).toEqual({
|
|
699
|
+
mode: "fork",
|
|
700
|
+
threadId: "child-thread-A",
|
|
701
|
+
});
|
|
702
|
+
expect(workflowInput.sandbox).toEqual({
|
|
703
|
+
mode: "fork",
|
|
704
|
+
sandboxId: "child-sb-1",
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it("does not pass sandbox fork without thread continuation", async () => {
|
|
709
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
710
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
711
|
+
|
|
712
|
+
nextStartChildResult = () => ({
|
|
713
|
+
toolResponse: "done",
|
|
714
|
+
data: null,
|
|
715
|
+
threadId: "child-thread-A",
|
|
716
|
+
sandboxId: "child-sb-1",
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
const contSandboxSubagent: SubagentConfig = {
|
|
720
|
+
agentName: "sb-cont",
|
|
721
|
+
description: "Sandbox continuation",
|
|
722
|
+
workflow: mockWorkflow(),
|
|
723
|
+
thread: "fork",
|
|
724
|
+
sandbox: "own",
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const { handler } = createSubagentHandler([contSandboxSubagent]);
|
|
728
|
+
|
|
729
|
+
await handler(
|
|
730
|
+
{ subagent: "sb-cont", description: "test", prompt: "first" },
|
|
731
|
+
{ threadId: "t", toolCallId: "tc-1", toolName: "Subagent" }
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
nextStartChildResult = () => ({
|
|
735
|
+
toolResponse: "new run",
|
|
736
|
+
data: null,
|
|
737
|
+
threadId: "child-thread-B",
|
|
738
|
+
sandboxId: "child-sb-2",
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
await handler(
|
|
742
|
+
{ subagent: "sb-cont", description: "test", prompt: "no continuation" },
|
|
743
|
+
{ threadId: "t", toolCallId: "tc-2", toolName: "Subagent" }
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
const secondCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
747
|
+
if (!secondCall) throw new Error("expected startChild call");
|
|
748
|
+
const workflowInput = secondCall[1].args[1] as SubagentWorkflowInput;
|
|
749
|
+
expect(workflowInput.sandbox).toBeUndefined();
|
|
750
|
+
expect(workflowInput.thread).toBeUndefined();
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it("adds fork-mode subagent to pendingDestroys", async () => {
|
|
754
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
755
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
756
|
+
|
|
757
|
+
const contSandboxSubagent: SubagentConfig = {
|
|
758
|
+
agentName: "sb-cont",
|
|
759
|
+
description: "Sandbox continuation",
|
|
760
|
+
workflow: mockWorkflow(),
|
|
761
|
+
thread: "fork",
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
765
|
+
contSandboxSubagent,
|
|
766
|
+
]);
|
|
767
|
+
|
|
768
|
+
await handler(
|
|
769
|
+
{ subagent: "sb-cont", description: "test", prompt: "run" },
|
|
770
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
await destroySubagentSandboxes();
|
|
774
|
+
|
|
775
|
+
const lastResult = startMock.mock.results.at(-1);
|
|
776
|
+
if (!lastResult) throw new Error("expected startChild call");
|
|
777
|
+
const childHandle = await lastResult.value;
|
|
778
|
+
expect(childHandle.signal).toHaveBeenCalled();
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
it("signals destroy and awaits result for sandbox=own subagents at cleanup", async () => {
|
|
782
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
783
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
784
|
+
|
|
335
785
|
const ownSubagent: SubagentConfig = {
|
|
336
786
|
agentName: "own-agent",
|
|
337
787
|
description: "Own sandbox",
|
|
338
|
-
workflow:
|
|
788
|
+
workflow: mockWorkflow(),
|
|
339
789
|
sandbox: "own",
|
|
340
790
|
};
|
|
341
791
|
|
|
342
|
-
const handler = createSubagentHandler([
|
|
792
|
+
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
793
|
+
ownSubagent,
|
|
794
|
+
]);
|
|
343
795
|
|
|
344
796
|
await handler(
|
|
345
|
-
{ subagent: "own-agent", description: "test", prompt: "
|
|
797
|
+
{ subagent: "own-agent", description: "test", prompt: "run" },
|
|
798
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
await destroySubagentSandboxes();
|
|
802
|
+
|
|
803
|
+
const lastResult = startMock.mock.results.at(-1);
|
|
804
|
+
if (!lastResult) throw new Error("expected startChild call");
|
|
805
|
+
const childHandle = await lastResult.value;
|
|
806
|
+
expect(childHandle.signal).toHaveBeenCalled();
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it("does not signal destroy for inherit subagents", async () => {
|
|
810
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
811
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
812
|
+
|
|
813
|
+
const inheritSubagent: SubagentConfig = {
|
|
814
|
+
agentName: "inherit-agent",
|
|
815
|
+
description: "Inherits sandbox",
|
|
816
|
+
workflow: mockWorkflow(),
|
|
817
|
+
sandbox: "inherit",
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
821
|
+
inheritSubagent,
|
|
822
|
+
]);
|
|
823
|
+
|
|
824
|
+
await handler(
|
|
825
|
+
{ subagent: "inherit-agent", description: "test", prompt: "run" },
|
|
826
|
+
{
|
|
827
|
+
threadId: "t",
|
|
828
|
+
toolCallId: "tc",
|
|
829
|
+
toolName: "Subagent",
|
|
830
|
+
sandboxId: "parent-sb",
|
|
831
|
+
}
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
const lastResult = startMock.mock.results.at(-1);
|
|
835
|
+
if (!lastResult) throw new Error("expected startChild call");
|
|
836
|
+
const childHandle = await lastResult.value;
|
|
837
|
+
childHandle.signal.mockClear();
|
|
838
|
+
|
|
839
|
+
await destroySubagentSandboxes();
|
|
840
|
+
|
|
841
|
+
expect(childHandle.signal).not.toHaveBeenCalled();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it("does not pass sandboxId when sandbox is none (default)", async () => {
|
|
845
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
846
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
847
|
+
|
|
848
|
+
const noneSubagent: SubagentConfig = {
|
|
849
|
+
agentName: "none-agent",
|
|
850
|
+
description: "No sandbox",
|
|
851
|
+
workflow: mockWorkflow(),
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
const { handler } = createSubagentHandler([noneSubagent]);
|
|
855
|
+
|
|
856
|
+
await handler(
|
|
857
|
+
{ subagent: "none-agent", description: "test", prompt: "test" },
|
|
858
|
+
{
|
|
859
|
+
threadId: "t",
|
|
860
|
+
toolCallId: "tc",
|
|
861
|
+
toolName: "Subagent",
|
|
862
|
+
sandboxId: "parent-sb",
|
|
863
|
+
}
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
867
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
868
|
+
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
869
|
+
expect(workflowInput.sandbox).toBeUndefined();
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
it("does not pass sandboxId when sandbox is explicitly none", async () => {
|
|
873
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
874
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
875
|
+
|
|
876
|
+
const noneSubagent: SubagentConfig = {
|
|
877
|
+
agentName: "none-agent",
|
|
878
|
+
description: "No sandbox",
|
|
879
|
+
workflow: mockWorkflow(),
|
|
880
|
+
sandbox: "none",
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
const { handler } = createSubagentHandler([noneSubagent]);
|
|
884
|
+
|
|
885
|
+
await handler(
|
|
886
|
+
{ subagent: "none-agent", description: "test", prompt: "test" },
|
|
346
887
|
{
|
|
347
888
|
threadId: "t",
|
|
348
889
|
toolCallId: "tc",
|
|
@@ -351,10 +892,163 @@ describe("createSubagentHandler", () => {
|
|
|
351
892
|
}
|
|
352
893
|
);
|
|
353
894
|
|
|
354
|
-
const lastCall =
|
|
355
|
-
if (!lastCall) throw new Error("expected
|
|
895
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
896
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
356
897
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
357
|
-
expect(workflowInput.
|
|
898
|
+
expect(workflowInput.sandbox).toBeUndefined();
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
it("does not signal destroy for none subagents", async () => {
|
|
902
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
903
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
904
|
+
|
|
905
|
+
const noneSubagent: SubagentConfig = {
|
|
906
|
+
agentName: "none-agent",
|
|
907
|
+
description: "No sandbox",
|
|
908
|
+
workflow: mockWorkflow(),
|
|
909
|
+
sandbox: "none",
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
913
|
+
noneSubagent,
|
|
914
|
+
]);
|
|
915
|
+
|
|
916
|
+
await handler(
|
|
917
|
+
{ subagent: "none-agent", description: "test", prompt: "run" },
|
|
918
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
919
|
+
);
|
|
920
|
+
|
|
921
|
+
const lastResult = startMock.mock.results.at(-1);
|
|
922
|
+
if (!lastResult) throw new Error("expected startChild call");
|
|
923
|
+
const childHandle = await lastResult.value;
|
|
924
|
+
childHandle.signal.mockClear();
|
|
925
|
+
|
|
926
|
+
await destroySubagentSandboxes();
|
|
927
|
+
|
|
928
|
+
expect(childHandle.signal).not.toHaveBeenCalled();
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
it("returns sandboxId in response when child creates a sandbox", async () => {
|
|
932
|
+
nextStartChildResult = () => ({
|
|
933
|
+
toolResponse: "done",
|
|
934
|
+
data: null,
|
|
935
|
+
threadId: "child-t",
|
|
936
|
+
sandboxId: "child-sb-42",
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
const ownSubagent: SubagentConfig = {
|
|
940
|
+
agentName: "own-agent",
|
|
941
|
+
description: "Own sandbox",
|
|
942
|
+
workflow: mockWorkflow(),
|
|
943
|
+
sandbox: "own",
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
const { handler } = createSubagentHandler([ownSubagent]);
|
|
947
|
+
|
|
948
|
+
const result = await handler(
|
|
949
|
+
{ subagent: "own-agent", description: "test", prompt: "test" },
|
|
950
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
expect(result.sandboxId).toBe("child-sb-42");
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
it("does not include sandboxId in response when child has none", async () => {
|
|
957
|
+
nextStartChildResult = () => ({
|
|
958
|
+
toolResponse: "done",
|
|
959
|
+
data: null,
|
|
960
|
+
threadId: "child-t",
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
const { handler } = createSubagentHandler([basicSubagent]);
|
|
964
|
+
|
|
965
|
+
const result = await handler(
|
|
966
|
+
{ subagent: "researcher", description: "test", prompt: "test" },
|
|
967
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
968
|
+
);
|
|
969
|
+
|
|
970
|
+
expect(result.sandboxId).toBeUndefined();
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
it("passes metadata through on success", async () => {
|
|
974
|
+
nextStartChildResult = () => ({
|
|
975
|
+
toolResponse: "result",
|
|
976
|
+
data: { result: "ok" },
|
|
977
|
+
threadId: "child-t",
|
|
978
|
+
metadata: { jobId: "j-123", env: "staging" },
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
const { handler } = createSubagentHandler([basicSubagent]);
|
|
982
|
+
|
|
983
|
+
const result = await handler(
|
|
984
|
+
{ subagent: "researcher", description: "test", prompt: "test" },
|
|
985
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
expect(result.metadata).toEqual({ jobId: "j-123", env: "staging" });
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
it("passes metadata through when toolResponse is null", async () => {
|
|
992
|
+
nextStartChildResult = () => ({
|
|
993
|
+
toolResponse: null,
|
|
994
|
+
data: null,
|
|
995
|
+
threadId: "child-t",
|
|
996
|
+
metadata: { state: "pending" },
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
const { handler } = createSubagentHandler([basicSubagent]);
|
|
1000
|
+
|
|
1001
|
+
const result = await handler(
|
|
1002
|
+
{ subagent: "researcher", description: "test", prompt: "test" },
|
|
1003
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
expect(result.toolResponse).toContain("no response");
|
|
1007
|
+
expect(result.metadata).toEqual({ state: "pending" });
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
it("passes metadata through when validation fails", async () => {
|
|
1011
|
+
nextStartChildResult = () => ({
|
|
1012
|
+
toolResponse: "result",
|
|
1013
|
+
data: { wrong: "shape" },
|
|
1014
|
+
threadId: "child-t",
|
|
1015
|
+
metadata: { deployId: "d-456" },
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
const validatedSubagent: SubagentConfig = {
|
|
1019
|
+
agentName: "validated",
|
|
1020
|
+
description: "Has validation",
|
|
1021
|
+
workflow: mockWorkflow(),
|
|
1022
|
+
resultSchema: z.object({ expected: z.string() }),
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
const { handler } = createSubagentHandler([validatedSubagent]);
|
|
1026
|
+
|
|
1027
|
+
const result = await handler(
|
|
1028
|
+
{ subagent: "validated", description: "test", prompt: "test" },
|
|
1029
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
1030
|
+
);
|
|
1031
|
+
|
|
1032
|
+
expect(result.toolResponse).toContain("invalid data");
|
|
1033
|
+
expect(result.data).toBeNull();
|
|
1034
|
+
expect(result.metadata).toEqual({ deployId: "d-456" });
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
it("omits metadata when child does not return it", async () => {
|
|
1038
|
+
nextStartChildResult = () => ({
|
|
1039
|
+
toolResponse: "result",
|
|
1040
|
+
data: null,
|
|
1041
|
+
threadId: "child-t",
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
const { handler } = createSubagentHandler([basicSubagent]);
|
|
1045
|
+
|
|
1046
|
+
const result = await handler(
|
|
1047
|
+
{ subagent: "researcher", description: "test", prompt: "test" },
|
|
1048
|
+
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
expect(result.metadata).toBeUndefined();
|
|
358
1052
|
});
|
|
359
1053
|
});
|
|
360
1054
|
|
|
@@ -372,15 +1066,14 @@ describe("buildSubagentRegistration", () => {
|
|
|
372
1066
|
{
|
|
373
1067
|
agentName: "agent",
|
|
374
1068
|
description: "An agent",
|
|
375
|
-
workflow:
|
|
1069
|
+
workflow: mockWorkflow(),
|
|
376
1070
|
},
|
|
377
1071
|
]);
|
|
378
1072
|
|
|
379
1073
|
expect(reg).not.toBeNull();
|
|
380
|
-
expect(reg).toBeDefined();
|
|
381
1074
|
if (reg) {
|
|
382
|
-
expect(reg.name).toBe(SUBAGENT_TOOL_NAME);
|
|
383
|
-
expect(typeof reg.handler).toBe("function");
|
|
1075
|
+
expect(reg.registration.name).toBe(SUBAGENT_TOOL_NAME);
|
|
1076
|
+
expect(typeof reg.registration.handler).toBe("function");
|
|
384
1077
|
}
|
|
385
1078
|
});
|
|
386
1079
|
|
|
@@ -390,17 +1083,17 @@ describe("buildSubagentRegistration", () => {
|
|
|
390
1083
|
{
|
|
391
1084
|
agentName: "toggle",
|
|
392
1085
|
description: "Toggleable",
|
|
393
|
-
workflow:
|
|
1086
|
+
workflow: mockWorkflow(),
|
|
394
1087
|
enabled: () => flag,
|
|
395
1088
|
},
|
|
396
1089
|
]);
|
|
397
1090
|
|
|
398
1091
|
expect(reg).toBeDefined();
|
|
399
1092
|
if (!reg) return;
|
|
400
|
-
expect((reg.enabled as () => boolean)()).toBe(true);
|
|
1093
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(true);
|
|
401
1094
|
|
|
402
1095
|
flag = false;
|
|
403
|
-
expect((reg.enabled as () => boolean)()).toBe(false);
|
|
1096
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(false);
|
|
404
1097
|
});
|
|
405
1098
|
|
|
406
1099
|
it("disabled when all subagents are disabled", () => {
|
|
@@ -408,14 +1101,14 @@ describe("buildSubagentRegistration", () => {
|
|
|
408
1101
|
{
|
|
409
1102
|
agentName: "off",
|
|
410
1103
|
description: "Disabled",
|
|
411
|
-
workflow:
|
|
1104
|
+
workflow: mockWorkflow(),
|
|
412
1105
|
enabled: false,
|
|
413
1106
|
},
|
|
414
1107
|
]);
|
|
415
1108
|
|
|
416
1109
|
expect(reg).toBeDefined();
|
|
417
1110
|
if (reg) {
|
|
418
|
-
expect((reg.enabled as () => boolean)()).toBe(false);
|
|
1111
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(false);
|
|
419
1112
|
}
|
|
420
1113
|
});
|
|
421
1114
|
|
|
@@ -426,7 +1119,7 @@ describe("buildSubagentRegistration", () => {
|
|
|
426
1119
|
{
|
|
427
1120
|
agentName: "hooked",
|
|
428
1121
|
description: "Has hooks",
|
|
429
|
-
workflow:
|
|
1122
|
+
workflow: mockWorkflow(),
|
|
430
1123
|
hooks: {
|
|
431
1124
|
onPreExecution: hookSpy,
|
|
432
1125
|
},
|
|
@@ -435,9 +1128,9 @@ describe("buildSubagentRegistration", () => {
|
|
|
435
1128
|
|
|
436
1129
|
expect(reg).toBeDefined();
|
|
437
1130
|
if (reg) {
|
|
438
|
-
expect(reg.hooks).toBeDefined();
|
|
439
|
-
if (reg.hooks) {
|
|
440
|
-
expect(reg.hooks.onPreToolUse).toBeDefined();
|
|
1131
|
+
expect(reg.registration.hooks).toBeDefined();
|
|
1132
|
+
if (reg.registration.hooks) {
|
|
1133
|
+
expect(reg.registration.hooks.onPreToolUse).toBeDefined();
|
|
441
1134
|
}
|
|
442
1135
|
}
|
|
443
1136
|
});
|
|
@@ -447,13 +1140,13 @@ describe("buildSubagentRegistration", () => {
|
|
|
447
1140
|
{
|
|
448
1141
|
agentName: "plain",
|
|
449
1142
|
description: "No hooks",
|
|
450
|
-
workflow:
|
|
1143
|
+
workflow: mockWorkflow(),
|
|
451
1144
|
},
|
|
452
1145
|
]);
|
|
453
1146
|
|
|
454
1147
|
expect(reg).toBeDefined();
|
|
455
1148
|
if (reg) {
|
|
456
|
-
expect(reg.hooks).toBeUndefined();
|
|
1149
|
+
expect(reg.registration.hooks).toBeUndefined();
|
|
457
1150
|
}
|
|
458
1151
|
});
|
|
459
1152
|
|
|
@@ -463,20 +1156,20 @@ describe("buildSubagentRegistration", () => {
|
|
|
463
1156
|
{
|
|
464
1157
|
agentName: "a",
|
|
465
1158
|
description: "Agent A",
|
|
466
|
-
workflow:
|
|
1159
|
+
workflow: mockWorkflow(),
|
|
467
1160
|
enabled: true,
|
|
468
1161
|
},
|
|
469
1162
|
{
|
|
470
1163
|
agentName: "b",
|
|
471
1164
|
description: "Agent B",
|
|
472
|
-
workflow:
|
|
1165
|
+
workflow: mockWorkflow(),
|
|
473
1166
|
enabled: () => bEnabled,
|
|
474
1167
|
},
|
|
475
1168
|
]);
|
|
476
1169
|
|
|
477
1170
|
expect(reg).toBeDefined();
|
|
478
1171
|
if (reg) {
|
|
479
|
-
const desc = reg.description as () => string;
|
|
1172
|
+
const desc = reg.registration.description as () => string;
|
|
480
1173
|
expect(desc()).toContain("Agent A");
|
|
481
1174
|
expect(desc()).toContain("Agent B");
|
|
482
1175
|
|
|
@@ -520,10 +1213,24 @@ describe("defineSubagent", () => {
|
|
|
520
1213
|
const reg = buildSubagentRegistration([config]);
|
|
521
1214
|
expect(reg).toBeDefined();
|
|
522
1215
|
if (!reg) return;
|
|
523
|
-
expect((reg.enabled as () => boolean)()).toBe(true);
|
|
1216
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(true);
|
|
524
1217
|
|
|
525
1218
|
flag = false;
|
|
526
|
-
expect((reg.enabled as () => boolean)()).toBe(false);
|
|
1219
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(false);
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
it("passes sandbox none through to config", () => {
|
|
1223
|
+
const config = defineSubagent(makeDef("no-sb"), {
|
|
1224
|
+
sandbox: "none",
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
expect(config.sandbox).toBe("none");
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
it("defaults sandbox to undefined (none behavior)", () => {
|
|
1231
|
+
const config = defineSubagent(makeDef("default-sb"));
|
|
1232
|
+
|
|
1233
|
+
expect(config.sandbox).toBeUndefined();
|
|
527
1234
|
});
|
|
528
1235
|
});
|
|
529
1236
|
|
|
@@ -532,30 +1239,27 @@ describe("defineSubagent", () => {
|
|
|
532
1239
|
// ---------------------------------------------------------------------------
|
|
533
1240
|
|
|
534
1241
|
describe("defineSubagentWorkflow", () => {
|
|
535
|
-
it("maps
|
|
536
|
-
let capturedPrompt: string | undefined;
|
|
1242
|
+
it("maps thread fork into sessionInput", async () => {
|
|
537
1243
|
let capturedSession: SubagentSessionInput | undefined;
|
|
538
1244
|
|
|
539
1245
|
const workflow = defineSubagentWorkflow(
|
|
540
1246
|
{ name: "test", description: "test agent" },
|
|
541
|
-
async (
|
|
542
|
-
capturedPrompt = prompt;
|
|
1247
|
+
async (_prompt, sessionInput) => {
|
|
543
1248
|
capturedSession = sessionInput;
|
|
544
1249
|
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
545
1250
|
}
|
|
546
1251
|
);
|
|
547
1252
|
|
|
548
|
-
await workflow("go", {
|
|
1253
|
+
await workflow("go", { thread: { mode: "fork", threadId: "prev-42" } });
|
|
549
1254
|
|
|
550
|
-
expect(capturedPrompt).toBe("go");
|
|
551
1255
|
expect(capturedSession).toEqual({
|
|
552
1256
|
agentName: "test",
|
|
553
|
-
|
|
554
|
-
|
|
1257
|
+
sandboxShutdown: "destroy",
|
|
1258
|
+
thread: { mode: "fork", threadId: "prev-42" },
|
|
555
1259
|
});
|
|
556
1260
|
});
|
|
557
1261
|
|
|
558
|
-
it("maps
|
|
1262
|
+
it("maps sandbox inherit", async () => {
|
|
559
1263
|
let capturedSession: SubagentSessionInput | undefined;
|
|
560
1264
|
const workflow = defineSubagentWorkflow(
|
|
561
1265
|
{ name: "test", description: "test agent" },
|
|
@@ -565,8 +1269,52 @@ describe("defineSubagentWorkflow", () => {
|
|
|
565
1269
|
}
|
|
566
1270
|
);
|
|
567
1271
|
|
|
568
|
-
await workflow("go", { sandboxId: "sb-123" });
|
|
569
|
-
expect(capturedSession).toEqual({
|
|
1272
|
+
await workflow("go", { sandbox: { mode: "inherit", sandboxId: "sb-123" } });
|
|
1273
|
+
expect(capturedSession).toEqual({
|
|
1274
|
+
agentName: "test",
|
|
1275
|
+
sandboxShutdown: "destroy",
|
|
1276
|
+
sandbox: { mode: "inherit", sandboxId: "sb-123" },
|
|
1277
|
+
});
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
it("maps sandbox fork", async () => {
|
|
1281
|
+
let capturedSession: SubagentSessionInput | undefined;
|
|
1282
|
+
const workflow = defineSubagentWorkflow(
|
|
1283
|
+
{ name: "test", description: "test agent" },
|
|
1284
|
+
async (_prompt, sessionInput) => {
|
|
1285
|
+
capturedSession = sessionInput;
|
|
1286
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
1287
|
+
}
|
|
1288
|
+
);
|
|
1289
|
+
|
|
1290
|
+
await workflow("go", { sandbox: { mode: "fork", sandboxId: "prev-sb-1" } });
|
|
1291
|
+
expect(capturedSession).toEqual({
|
|
1292
|
+
agentName: "test",
|
|
1293
|
+
sandboxShutdown: "destroy",
|
|
1294
|
+
sandbox: { mode: "fork", sandboxId: "prev-sb-1" },
|
|
1295
|
+
});
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
it("maps thread fork and sandbox fork together", async () => {
|
|
1299
|
+
let capturedSession: SubagentSessionInput | undefined;
|
|
1300
|
+
const workflow = defineSubagentWorkflow(
|
|
1301
|
+
{ name: "test", description: "test agent" },
|
|
1302
|
+
async (_prompt, sessionInput) => {
|
|
1303
|
+
capturedSession = sessionInput;
|
|
1304
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
1305
|
+
}
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
await workflow("go", {
|
|
1309
|
+
thread: { mode: "fork", threadId: "prev-t" },
|
|
1310
|
+
sandbox: { mode: "fork", sandboxId: "prev-sb" },
|
|
1311
|
+
});
|
|
1312
|
+
expect(capturedSession).toEqual({
|
|
1313
|
+
agentName: "test",
|
|
1314
|
+
sandboxShutdown: "destroy",
|
|
1315
|
+
thread: { mode: "fork", threadId: "prev-t" },
|
|
1316
|
+
sandbox: { mode: "fork", sandboxId: "prev-sb" },
|
|
1317
|
+
});
|
|
570
1318
|
});
|
|
571
1319
|
|
|
572
1320
|
it("passes context as optional third argument", async () => {
|
|
@@ -620,4 +1368,21 @@ describe("defineSubagentWorkflow", () => {
|
|
|
620
1368
|
expect(workflow.description).toBe("Researches topics");
|
|
621
1369
|
expect(workflow.resultSchema).toBe(schema);
|
|
622
1370
|
});
|
|
1371
|
+
|
|
1372
|
+
it("passes empty workflowInput fields as empty sessionInput", async () => {
|
|
1373
|
+
let capturedSession: SubagentSessionInput | undefined;
|
|
1374
|
+
const workflow = defineSubagentWorkflow(
|
|
1375
|
+
{ name: "test", description: "test agent" },
|
|
1376
|
+
async (_prompt, sessionInput) => {
|
|
1377
|
+
capturedSession = sessionInput;
|
|
1378
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
1379
|
+
}
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
await workflow("go", {});
|
|
1383
|
+
expect(capturedSession).toEqual({
|
|
1384
|
+
agentName: "test",
|
|
1385
|
+
sandboxShutdown: "destroy",
|
|
1386
|
+
});
|
|
1387
|
+
});
|
|
623
1388
|
});
|