zeitlich 0.2.22 → 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 +242 -59
- package/dist/adapters/sandbox/daytona/index.cjs +4 -1
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +2 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +2 -1
- package/dist/adapters/sandbox/daytona/index.js +4 -1
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +1 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.js +1 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +16 -2
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +3 -2
- package/dist/adapters/sandbox/inmemory/index.d.ts +3 -2
- package/dist/adapters/sandbox/inmemory/index.js +16 -2
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.js +1 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/sandbox/virtual/index.cjs +33 -9
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +6 -5
- package/dist/adapters/sandbox/virtual/index.d.ts +6 -5
- package/dist/adapters/sandbox/virtual/index.js +33 -9
- package/dist/adapters/sandbox/virtual/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.cjs +1 -0
- package/dist/adapters/sandbox/virtual/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/virtual/workflow.d.cts +3 -3
- package/dist/adapters/sandbox/virtual/workflow.d.ts +3 -3
- package/dist/adapters/sandbox/virtual/workflow.js +1 -0
- package/dist/adapters/sandbox/virtual/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +3 -3
- package/dist/adapters/thread/google-genai/index.d.ts +3 -3
- package/dist/adapters/thread/google-genai/workflow.d.cts +3 -3
- package/dist/adapters/thread/google-genai/workflow.d.ts +3 -3
- package/dist/adapters/thread/langchain/index.d.cts +3 -3
- package/dist/adapters/thread/langchain/index.d.ts +3 -3
- package/dist/adapters/thread/langchain/workflow.d.cts +3 -3
- package/dist/adapters/thread/langchain/workflow.d.ts +3 -3
- package/dist/index.cjs +247 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +245 -55
- package/dist/index.js.map +1 -1
- package/dist/{queries-Bw6WEPMw.d.cts → queries-DModcWRy.d.cts} +1 -1
- package/dist/{queries-C27raDaB.d.ts → queries-byD0jr1Y.d.ts} +1 -1
- package/dist/{types-ClsHhtwL.d.cts → types-B50pBPEV.d.ts} +159 -35
- 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/{types-BJ8itUAl.d.cts → types-BuXdFhaZ.d.cts} +6 -6
- package/dist/{types-HBosetv3.d.cts → types-ChAMwU3q.d.cts} +2 -0
- package/dist/{types-HBosetv3.d.ts → types-ChAMwU3q.d.ts} +2 -0
- package/dist/{types-C5bkx6kQ.d.ts → types-DQW8l7pY.d.cts} +159 -35
- package/dist/{types-ENYCKFBk.d.ts → types-GZ76HZSj.d.ts} +6 -6
- package/dist/workflow.cjs +241 -57
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +49 -32
- package/dist/workflow.d.ts +49 -32
- package/dist/workflow.js +239 -55
- package/dist/workflow.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/sandbox/daytona/filesystem.ts +1 -1
- package/src/adapters/sandbox/daytona/index.ts +4 -0
- package/src/adapters/sandbox/daytona/proxy.ts +4 -3
- package/src/adapters/sandbox/e2b/index.ts +5 -0
- package/src/adapters/sandbox/inmemory/index.ts +24 -4
- package/src/adapters/sandbox/inmemory/proxy.ts +2 -2
- package/src/adapters/sandbox/virtual/filesystem.ts +41 -17
- package/src/adapters/sandbox/virtual/provider.ts +4 -0
- package/src/adapters/sandbox/virtual/proxy.ts +1 -0
- package/src/adapters/sandbox/virtual/types.ts +9 -4
- package/src/lib/lifecycle.ts +57 -0
- package/src/lib/sandbox/manager.ts +13 -1
- package/src/lib/sandbox/types.ts +13 -4
- package/src/lib/session/index.ts +1 -0
- package/src/lib/session/session-edge-cases.integration.test.ts +447 -33
- package/src/lib/session/session.integration.test.ts +52 -32
- package/src/lib/session/session.ts +107 -33
- package/src/lib/session/types.ts +55 -16
- package/src/lib/subagent/define.ts +5 -4
- package/src/lib/subagent/handler.ts +139 -14
- package/src/lib/subagent/index.ts +3 -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 +853 -150
- package/src/lib/subagent/tool.ts +2 -2
- package/src/lib/subagent/types.ts +77 -19
- package/src/lib/subagent/workflow.ts +83 -12
- package/src/lib/tool-router/router.integration.test.ts +137 -4
- package/src/lib/tool-router/router.ts +13 -3
- package/src/lib/tool-router/types.ts +7 -0
- package/src/lib/workflow.test.ts +89 -21
- package/src/lib/workflow.ts +33 -18
- package/src/workflow.ts +6 -1
|
@@ -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,94 +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");
|
|
322
390
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
323
|
-
expect(workflowInput.
|
|
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");
|
|
442
|
+
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
443
|
+
expect(workflowInput.sandbox).toBeUndefined();
|
|
324
444
|
});
|
|
325
445
|
|
|
326
446
|
it("resolves context function at invocation time", async () => {
|
|
327
|
-
const {
|
|
328
|
-
const
|
|
329
|
-
execMock.mockResolvedValueOnce({
|
|
330
|
-
toolResponse: "ok",
|
|
331
|
-
data: null,
|
|
332
|
-
threadId: "child-t",
|
|
333
|
-
});
|
|
447
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
448
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
334
449
|
|
|
335
450
|
let counter = 0;
|
|
336
451
|
const dynamicSubagent: SubagentConfig = {
|
|
337
452
|
agentName: "dynamic-ctx",
|
|
338
453
|
description: "Dynamic context",
|
|
339
|
-
workflow:
|
|
454
|
+
workflow: mockWorkflow(),
|
|
340
455
|
context: () => {
|
|
341
456
|
counter++;
|
|
342
457
|
return { invocation: counter };
|
|
343
458
|
},
|
|
344
459
|
};
|
|
345
460
|
|
|
346
|
-
const handler = createSubagentHandler([dynamicSubagent]);
|
|
461
|
+
const { handler } = createSubagentHandler([dynamicSubagent]);
|
|
347
462
|
|
|
348
463
|
await handler(
|
|
349
464
|
{ subagent: "dynamic-ctx", description: "test", prompt: "test" },
|
|
350
465
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
351
466
|
);
|
|
352
467
|
|
|
353
|
-
const lastCall =
|
|
354
|
-
if (!lastCall) throw new Error("expected
|
|
468
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
469
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
355
470
|
const context = lastCall[1].args[2] as Record<string, unknown>;
|
|
356
471
|
expect(context).toEqual({ invocation: 1 });
|
|
357
472
|
});
|
|
358
473
|
|
|
359
474
|
it("passes static context unchanged", async () => {
|
|
360
|
-
const {
|
|
361
|
-
const
|
|
362
|
-
execMock.mockResolvedValueOnce({
|
|
363
|
-
toolResponse: "ok",
|
|
364
|
-
data: null,
|
|
365
|
-
threadId: "child-t",
|
|
366
|
-
});
|
|
475
|
+
const { startChild } = await import("@temporalio/workflow");
|
|
476
|
+
const startMock = startChild as ReturnType<typeof vi.fn>;
|
|
367
477
|
|
|
368
478
|
const staticSubagent: SubagentConfig = {
|
|
369
479
|
agentName: "static-ctx",
|
|
370
480
|
description: "Static context",
|
|
371
|
-
workflow:
|
|
481
|
+
workflow: mockWorkflow(),
|
|
372
482
|
context: { key: "value" },
|
|
373
483
|
};
|
|
374
484
|
|
|
375
|
-
const handler = createSubagentHandler([staticSubagent]);
|
|
485
|
+
const { handler } = createSubagentHandler([staticSubagent]);
|
|
376
486
|
|
|
377
487
|
await handler(
|
|
378
488
|
{ subagent: "static-ctx", description: "test", prompt: "test" },
|
|
379
489
|
{ threadId: "t", toolCallId: "tc", toolName: "Subagent" }
|
|
380
490
|
);
|
|
381
491
|
|
|
382
|
-
const lastCall =
|
|
383
|
-
if (!lastCall) throw new Error("expected
|
|
492
|
+
const lastCall = startMock.mock.calls[startMock.mock.calls.length - 1];
|
|
493
|
+
if (!lastCall) throw new Error("expected startChild call");
|
|
384
494
|
const context = lastCall[1].args[2] as Record<string, unknown>;
|
|
385
495
|
expect(context).toEqual({ key: "value" });
|
|
386
496
|
});
|
|
387
497
|
|
|
388
498
|
it("does not pass sandboxId when sandbox is own", async () => {
|
|
389
|
-
const {
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
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",
|
|
393
658
|
data: null,
|
|
394
|
-
threadId: "child-
|
|
659
|
+
threadId: "child-thread-A",
|
|
660
|
+
sandboxId: "child-sb-1",
|
|
661
|
+
});
|
|
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",
|
|
395
717
|
});
|
|
396
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
|
+
|
|
397
785
|
const ownSubagent: SubagentConfig = {
|
|
398
786
|
agentName: "own-agent",
|
|
399
787
|
description: "Own sandbox",
|
|
400
|
-
workflow:
|
|
788
|
+
workflow: mockWorkflow(),
|
|
401
789
|
sandbox: "own",
|
|
402
790
|
};
|
|
403
791
|
|
|
404
|
-
const handler = createSubagentHandler([
|
|
792
|
+
const { handler, destroySubagentSandboxes } = createSubagentHandler([
|
|
793
|
+
ownSubagent,
|
|
794
|
+
]);
|
|
405
795
|
|
|
406
796
|
await handler(
|
|
407
|
-
{ 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" },
|
|
408
887
|
{
|
|
409
888
|
threadId: "t",
|
|
410
889
|
toolCallId: "tc",
|
|
@@ -413,10 +892,163 @@ describe("createSubagentHandler", () => {
|
|
|
413
892
|
}
|
|
414
893
|
);
|
|
415
894
|
|
|
416
|
-
const lastCall =
|
|
417
|
-
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");
|
|
418
897
|
const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
|
|
419
|
-
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();
|
|
420
1052
|
});
|
|
421
1053
|
});
|
|
422
1054
|
|
|
@@ -434,15 +1066,14 @@ describe("buildSubagentRegistration", () => {
|
|
|
434
1066
|
{
|
|
435
1067
|
agentName: "agent",
|
|
436
1068
|
description: "An agent",
|
|
437
|
-
workflow:
|
|
1069
|
+
workflow: mockWorkflow(),
|
|
438
1070
|
},
|
|
439
1071
|
]);
|
|
440
1072
|
|
|
441
1073
|
expect(reg).not.toBeNull();
|
|
442
|
-
expect(reg).toBeDefined();
|
|
443
1074
|
if (reg) {
|
|
444
|
-
expect(reg.name).toBe(SUBAGENT_TOOL_NAME);
|
|
445
|
-
expect(typeof reg.handler).toBe("function");
|
|
1075
|
+
expect(reg.registration.name).toBe(SUBAGENT_TOOL_NAME);
|
|
1076
|
+
expect(typeof reg.registration.handler).toBe("function");
|
|
446
1077
|
}
|
|
447
1078
|
});
|
|
448
1079
|
|
|
@@ -452,17 +1083,17 @@ describe("buildSubagentRegistration", () => {
|
|
|
452
1083
|
{
|
|
453
1084
|
agentName: "toggle",
|
|
454
1085
|
description: "Toggleable",
|
|
455
|
-
workflow:
|
|
1086
|
+
workflow: mockWorkflow(),
|
|
456
1087
|
enabled: () => flag,
|
|
457
1088
|
},
|
|
458
1089
|
]);
|
|
459
1090
|
|
|
460
1091
|
expect(reg).toBeDefined();
|
|
461
1092
|
if (!reg) return;
|
|
462
|
-
expect((reg.enabled as () => boolean)()).toBe(true);
|
|
1093
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(true);
|
|
463
1094
|
|
|
464
1095
|
flag = false;
|
|
465
|
-
expect((reg.enabled as () => boolean)()).toBe(false);
|
|
1096
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(false);
|
|
466
1097
|
});
|
|
467
1098
|
|
|
468
1099
|
it("disabled when all subagents are disabled", () => {
|
|
@@ -470,14 +1101,14 @@ describe("buildSubagentRegistration", () => {
|
|
|
470
1101
|
{
|
|
471
1102
|
agentName: "off",
|
|
472
1103
|
description: "Disabled",
|
|
473
|
-
workflow:
|
|
1104
|
+
workflow: mockWorkflow(),
|
|
474
1105
|
enabled: false,
|
|
475
1106
|
},
|
|
476
1107
|
]);
|
|
477
1108
|
|
|
478
1109
|
expect(reg).toBeDefined();
|
|
479
1110
|
if (reg) {
|
|
480
|
-
expect((reg.enabled as () => boolean)()).toBe(false);
|
|
1111
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(false);
|
|
481
1112
|
}
|
|
482
1113
|
});
|
|
483
1114
|
|
|
@@ -488,7 +1119,7 @@ describe("buildSubagentRegistration", () => {
|
|
|
488
1119
|
{
|
|
489
1120
|
agentName: "hooked",
|
|
490
1121
|
description: "Has hooks",
|
|
491
|
-
workflow:
|
|
1122
|
+
workflow: mockWorkflow(),
|
|
492
1123
|
hooks: {
|
|
493
1124
|
onPreExecution: hookSpy,
|
|
494
1125
|
},
|
|
@@ -497,9 +1128,9 @@ describe("buildSubagentRegistration", () => {
|
|
|
497
1128
|
|
|
498
1129
|
expect(reg).toBeDefined();
|
|
499
1130
|
if (reg) {
|
|
500
|
-
expect(reg.hooks).toBeDefined();
|
|
501
|
-
if (reg.hooks) {
|
|
502
|
-
expect(reg.hooks.onPreToolUse).toBeDefined();
|
|
1131
|
+
expect(reg.registration.hooks).toBeDefined();
|
|
1132
|
+
if (reg.registration.hooks) {
|
|
1133
|
+
expect(reg.registration.hooks.onPreToolUse).toBeDefined();
|
|
503
1134
|
}
|
|
504
1135
|
}
|
|
505
1136
|
});
|
|
@@ -509,13 +1140,13 @@ describe("buildSubagentRegistration", () => {
|
|
|
509
1140
|
{
|
|
510
1141
|
agentName: "plain",
|
|
511
1142
|
description: "No hooks",
|
|
512
|
-
workflow:
|
|
1143
|
+
workflow: mockWorkflow(),
|
|
513
1144
|
},
|
|
514
1145
|
]);
|
|
515
1146
|
|
|
516
1147
|
expect(reg).toBeDefined();
|
|
517
1148
|
if (reg) {
|
|
518
|
-
expect(reg.hooks).toBeUndefined();
|
|
1149
|
+
expect(reg.registration.hooks).toBeUndefined();
|
|
519
1150
|
}
|
|
520
1151
|
});
|
|
521
1152
|
|
|
@@ -525,20 +1156,20 @@ describe("buildSubagentRegistration", () => {
|
|
|
525
1156
|
{
|
|
526
1157
|
agentName: "a",
|
|
527
1158
|
description: "Agent A",
|
|
528
|
-
workflow:
|
|
1159
|
+
workflow: mockWorkflow(),
|
|
529
1160
|
enabled: true,
|
|
530
1161
|
},
|
|
531
1162
|
{
|
|
532
1163
|
agentName: "b",
|
|
533
1164
|
description: "Agent B",
|
|
534
|
-
workflow:
|
|
1165
|
+
workflow: mockWorkflow(),
|
|
535
1166
|
enabled: () => bEnabled,
|
|
536
1167
|
},
|
|
537
1168
|
]);
|
|
538
1169
|
|
|
539
1170
|
expect(reg).toBeDefined();
|
|
540
1171
|
if (reg) {
|
|
541
|
-
const desc = reg.description as () => string;
|
|
1172
|
+
const desc = reg.registration.description as () => string;
|
|
542
1173
|
expect(desc()).toContain("Agent A");
|
|
543
1174
|
expect(desc()).toContain("Agent B");
|
|
544
1175
|
|
|
@@ -582,10 +1213,24 @@ describe("defineSubagent", () => {
|
|
|
582
1213
|
const reg = buildSubagentRegistration([config]);
|
|
583
1214
|
expect(reg).toBeDefined();
|
|
584
1215
|
if (!reg) return;
|
|
585
|
-
expect((reg.enabled as () => boolean)()).toBe(true);
|
|
1216
|
+
expect((reg.registration.enabled as () => boolean)()).toBe(true);
|
|
586
1217
|
|
|
587
1218
|
flag = false;
|
|
588
|
-
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();
|
|
589
1234
|
});
|
|
590
1235
|
});
|
|
591
1236
|
|
|
@@ -594,30 +1239,45 @@ describe("defineSubagent", () => {
|
|
|
594
1239
|
// ---------------------------------------------------------------------------
|
|
595
1240
|
|
|
596
1241
|
describe("defineSubagentWorkflow", () => {
|
|
597
|
-
it("maps
|
|
598
|
-
let capturedPrompt: string | undefined;
|
|
1242
|
+
it("maps thread fork into sessionInput", async () => {
|
|
599
1243
|
let capturedSession: SubagentSessionInput | undefined;
|
|
600
1244
|
|
|
601
1245
|
const workflow = defineSubagentWorkflow(
|
|
602
1246
|
{ name: "test", description: "test agent" },
|
|
603
|
-
async (
|
|
604
|
-
capturedPrompt = prompt;
|
|
1247
|
+
async (_prompt, sessionInput) => {
|
|
605
1248
|
capturedSession = sessionInput;
|
|
606
1249
|
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
607
1250
|
}
|
|
608
1251
|
);
|
|
609
1252
|
|
|
610
|
-
await workflow("go", {
|
|
1253
|
+
await workflow("go", { thread: { mode: "fork", threadId: "prev-42" } });
|
|
1254
|
+
|
|
1255
|
+
expect(capturedSession).toEqual({
|
|
1256
|
+
agentName: "test",
|
|
1257
|
+
sandboxShutdown: "destroy",
|
|
1258
|
+
thread: { mode: "fork", threadId: "prev-42" },
|
|
1259
|
+
});
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
it("maps sandbox inherit", async () => {
|
|
1263
|
+
let capturedSession: SubagentSessionInput | undefined;
|
|
1264
|
+
const workflow = defineSubagentWorkflow(
|
|
1265
|
+
{ name: "test", description: "test agent" },
|
|
1266
|
+
async (_prompt, sessionInput) => {
|
|
1267
|
+
capturedSession = sessionInput;
|
|
1268
|
+
return { toolResponse: "ok", data: null, threadId: "t" };
|
|
1269
|
+
}
|
|
1270
|
+
);
|
|
611
1271
|
|
|
612
|
-
|
|
1272
|
+
await workflow("go", { sandbox: { mode: "inherit", sandboxId: "sb-123" } });
|
|
613
1273
|
expect(capturedSession).toEqual({
|
|
614
1274
|
agentName: "test",
|
|
615
|
-
|
|
616
|
-
|
|
1275
|
+
sandboxShutdown: "destroy",
|
|
1276
|
+
sandbox: { mode: "inherit", sandboxId: "sb-123" },
|
|
617
1277
|
});
|
|
618
1278
|
});
|
|
619
1279
|
|
|
620
|
-
it("maps
|
|
1280
|
+
it("maps sandbox fork", async () => {
|
|
621
1281
|
let capturedSession: SubagentSessionInput | undefined;
|
|
622
1282
|
const workflow = defineSubagentWorkflow(
|
|
623
1283
|
{ name: "test", description: "test agent" },
|
|
@@ -627,8 +1287,34 @@ describe("defineSubagentWorkflow", () => {
|
|
|
627
1287
|
}
|
|
628
1288
|
);
|
|
629
1289
|
|
|
630
|
-
await workflow("go", { sandboxId: "sb-
|
|
631
|
-
expect(capturedSession).toEqual({
|
|
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
|
+
});
|
|
632
1318
|
});
|
|
633
1319
|
|
|
634
1320
|
it("passes context as optional third argument", async () => {
|
|
@@ -682,4 +1368,21 @@ describe("defineSubagentWorkflow", () => {
|
|
|
682
1368
|
expect(workflow.description).toBe("Researches topics");
|
|
683
1369
|
expect(workflow.resultSchema).toBe(schema);
|
|
684
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
|
+
});
|
|
685
1388
|
});
|