zeitlich 0.2.36 → 0.2.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +146 -92
- package/dist/{activities-BVI2lTwr.d.ts → activities-BKhMtKDd.d.ts} +4 -2
- package/dist/{activities-hd4aNnZE.d.cts → activities-CDcwkRZs.d.cts} +4 -2
- package/dist/adapters/sandbox/bedrock/index.cjs +17 -14
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +7 -6
- package/dist/adapters/sandbox/bedrock/index.d.ts +7 -6
- package/dist/adapters/sandbox/bedrock/index.js +17 -14
- package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.cjs +2 -0
- package/dist/adapters/sandbox/bedrock/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.js +2 -0
- package/dist/adapters/sandbox/bedrock/workflow.js.map +1 -1
- package/dist/adapters/sandbox/daytona/index.cjs +11 -3
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +5 -4
- package/dist/adapters/sandbox/daytona/index.d.ts +5 -4
- package/dist/adapters/sandbox/daytona/index.js +11 -3
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.cjs +2 -0
- package/dist/adapters/sandbox/daytona/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.js +2 -0
- package/dist/adapters/sandbox/daytona/workflow.js.map +1 -1
- package/dist/adapters/sandbox/e2b/index.cjs +73 -12
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +26 -4
- package/dist/adapters/sandbox/e2b/index.d.ts +26 -4
- package/dist/adapters/sandbox/e2b/index.js +73 -12
- package/dist/adapters/sandbox/e2b/index.js.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.cjs +2 -0
- package/dist/adapters/sandbox/e2b/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.js +2 -0
- package/dist/adapters/sandbox/e2b/workflow.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.cjs +8 -3
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +5 -4
- package/dist/adapters/sandbox/inmemory/index.d.ts +5 -4
- package/dist/adapters/sandbox/inmemory/index.js +8 -3
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +2 -0
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.js +2 -0
- package/dist/adapters/sandbox/inmemory/workflow.js.map +1 -1
- package/dist/adapters/thread/anthropic/index.cjs +94 -39
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +5 -5
- package/dist/adapters/thread/anthropic/index.d.ts +5 -5
- package/dist/adapters/thread/anthropic/index.js +94 -39
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +7 -2
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
- package/dist/adapters/thread/anthropic/workflow.js +7 -2
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +77 -28
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +5 -5
- package/dist/adapters/thread/google-genai/index.d.ts +5 -5
- package/dist/adapters/thread/google-genai/index.js +77 -28
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +7 -2
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
- package/dist/adapters/thread/google-genai/workflow.js +7 -2
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +57 -10
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +5 -5
- package/dist/adapters/thread/langchain/index.d.ts +5 -5
- package/dist/adapters/thread/langchain/index.js +57 -10
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +7 -2
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
- package/dist/adapters/thread/langchain/workflow.js +7 -2
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +322 -146
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -14
- package/dist/index.d.ts +20 -14
- package/dist/index.js +323 -147
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BjdFGPTm.d.ts → proxy-CUlKSvZS.d.ts} +1 -1
- package/dist/{proxy-7RnVaPdJ.d.cts → proxy-D_3x7RN4.d.cts} +1 -1
- package/dist/{thread-manager-CbpiGq1L.d.ts → thread-manager-CVu7o2cs.d.ts} +4 -2
- package/dist/{thread-manager-DzXm9eeI.d.cts → thread-manager-HSwyh28L.d.cts} +4 -2
- package/dist/{thread-manager-BBzNgQWH.d.cts → thread-manager-c1gPopAG.d.ts} +4 -2
- package/dist/{thread-manager-DjN5JYul.d.ts → thread-manager-wGi-LqIP.d.cts} +4 -2
- package/dist/{types-Mc_4BCfT.d.cts → types-BH_IRryz.d.ts} +10 -1
- package/dist/{types-yiXmqedU.d.ts → types-BaOw4hKI.d.cts} +10 -1
- package/dist/{types-DQ1l_gXL.d.cts → types-C06FwR96.d.cts} +121 -17
- package/dist/{types-wiGLvxWf.d.ts → types-DAsQ21Rt.d.ts} +1 -1
- package/dist/{types-CADc5V_P.d.ts → types-DNr31FzL.d.ts} +121 -17
- package/dist/{types-CBH54cwr.d.cts → types-lm8tMNJQ.d.cts} +1 -1
- package/dist/{types-DxCpFNv_.d.cts → types-yx0LzPGn.d.cts} +44 -5
- package/dist/{types-DxCpFNv_.d.ts → types-yx0LzPGn.d.ts} +44 -5
- package/dist/{workflow-DhtWRovz.d.cts → workflow-CSCkpwAL.d.ts} +2 -2
- package/dist/{workflow-P2pTSfKu.d.ts → workflow-DuvMZ8Vm.d.cts} +2 -2
- package/dist/workflow.cjs +274 -130
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +3 -3
- package/dist/workflow.d.ts +3 -3
- package/dist/workflow.js +275 -131
- package/dist/workflow.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/sandbox/bedrock/filesystem.ts +6 -12
- package/src/adapters/sandbox/bedrock/index.ts +22 -11
- package/src/adapters/sandbox/bedrock/proxy.ts +2 -0
- package/src/adapters/sandbox/daytona/index.ts +18 -3
- package/src/adapters/sandbox/daytona/proxy.ts +2 -0
- package/src/adapters/sandbox/e2b/filesystem.ts +5 -4
- package/src/adapters/sandbox/e2b/index.ts +87 -14
- package/src/adapters/sandbox/e2b/proxy.ts +2 -0
- package/src/adapters/sandbox/e2b/types.ts +16 -0
- package/src/adapters/sandbox/inmemory/index.ts +17 -3
- package/src/adapters/sandbox/inmemory/proxy.ts +2 -0
- package/src/adapters/thread/anthropic/activities.ts +58 -26
- package/src/adapters/thread/anthropic/model-invoker.ts +18 -7
- package/src/adapters/thread/anthropic/proxy.ts +6 -2
- package/src/adapters/thread/anthropic/thread-manager.test.ts +26 -7
- package/src/adapters/thread/anthropic/thread-manager.ts +63 -46
- package/src/adapters/thread/google-genai/activities.ts +20 -2
- package/src/adapters/thread/google-genai/model-invoker.ts +27 -7
- package/src/adapters/thread/google-genai/proxy.ts +6 -2
- package/src/adapters/thread/google-genai/thread-manager.test.ts +13 -3
- package/src/adapters/thread/google-genai/thread-manager.ts +57 -33
- package/src/adapters/thread/langchain/activities.ts +55 -24
- package/src/adapters/thread/langchain/hooks.test.ts +36 -49
- package/src/adapters/thread/langchain/hooks.ts +18 -5
- package/src/adapters/thread/langchain/model-invoker.ts +5 -4
- package/src/adapters/thread/langchain/proxy.ts +6 -2
- package/src/adapters/thread/langchain/thread-manager.test.ts +5 -1
- package/src/adapters/thread/langchain/thread-manager.ts +23 -9
- package/src/index.ts +4 -1
- package/src/lib/activity.ts +16 -6
- package/src/lib/hooks/types.ts +6 -6
- package/src/lib/lifecycle.ts +18 -3
- package/src/lib/model/proxy.ts +2 -2
- package/src/lib/model/types.ts +10 -0
- package/src/lib/observability/hooks.ts +4 -5
- package/src/lib/observability/index.ts +1 -4
- package/src/lib/sandbox/manager.ts +45 -20
- package/src/lib/sandbox/node-fs.ts +3 -6
- package/src/lib/sandbox/sandbox.test.ts +36 -3
- package/src/lib/sandbox/tree.integration.test.ts +10 -3
- package/src/lib/sandbox/types.ts +60 -6
- package/src/lib/session/session-edge-cases.integration.test.ts +316 -14
- package/src/lib/session/session.integration.test.ts +161 -1
- package/src/lib/session/session.ts +106 -21
- package/src/lib/session/types.ts +25 -5
- package/src/lib/skills/fs-provider.ts +12 -8
- package/src/lib/skills/handler.ts +1 -1
- package/src/lib/skills/parse.ts +3 -1
- package/src/lib/skills/register.ts +1 -3
- package/src/lib/skills/skills.integration.test.ts +25 -15
- package/src/lib/state/manager.integration.test.ts +12 -2
- package/src/lib/subagent/define.ts +1 -1
- package/src/lib/subagent/handler.ts +186 -71
- package/src/lib/subagent/index.ts +1 -5
- package/src/lib/subagent/register.ts +3 -2
- package/src/lib/subagent/signals.ts +1 -10
- package/src/lib/subagent/subagent.integration.test.ts +526 -248
- package/src/lib/subagent/tool.ts +4 -3
- package/src/lib/subagent/types.ts +50 -20
- package/src/lib/subagent/workflow.ts +9 -49
- package/src/lib/thread/id.test.ts +1 -1
- package/src/lib/thread/id.ts +1 -2
- package/src/lib/thread/manager.ts +18 -0
- package/src/lib/thread/proxy.ts +4 -4
- package/src/lib/thread/types.ts +20 -3
- package/src/lib/tool-router/index.ts +3 -5
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +93 -1
- package/src/lib/tool-router/router.integration.test.ts +12 -0
- package/src/lib/tool-router/router.ts +90 -16
- package/src/lib/tool-router/types.ts +45 -4
- package/src/lib/tool-router/with-sandbox.ts +19 -5
- package/src/lib/virtual-fs/filesystem.ts +1 -1
- package/src/lib/virtual-fs/index.ts +5 -1
- package/src/lib/virtual-fs/mutations.ts +2 -4
- package/src/lib/virtual-fs/queries.ts +9 -5
- package/src/lib/virtual-fs/types.ts +4 -1
- package/src/lib/virtual-fs/virtual-fs.test.ts +9 -11
- package/src/lib/workflow.test.ts +7 -4
- package/src/lib/workflow.ts +1 -5
- package/src/tools/ask-user-question/tool.ts +1 -3
- package/src/tools/glob/handler.ts +1 -4
- package/src/tools/task-get/handler.ts +4 -5
- package/src/tools/task-list/handler.ts +1 -4
- package/src/tools/task-update/handler.ts +4 -5
- package/src/workflow.ts +22 -7
- package/tsup.config.ts +9 -6
- package/src/lib/.env +0 -1
- package/src/tools/bash/.env +0 -1
|
@@ -47,7 +47,10 @@ Body content here.`;
|
|
|
47
47
|
expect(frontmatter.license).toBe("MIT");
|
|
48
48
|
expect(frontmatter.compatibility).toBe("linux-only");
|
|
49
49
|
expect(frontmatter.allowedTools).toEqual(["bash", "grep", "read-file"]);
|
|
50
|
-
expect(frontmatter.metadata).toEqual({
|
|
50
|
+
expect(frontmatter.metadata).toEqual({
|
|
51
|
+
author: "test-author",
|
|
52
|
+
version: "1.0",
|
|
53
|
+
});
|
|
51
54
|
expect(body).toBe("Body content here.");
|
|
52
55
|
});
|
|
53
56
|
|
|
@@ -76,7 +79,7 @@ Body`;
|
|
|
76
79
|
|
|
77
80
|
it("throws when frontmatter is missing", () => {
|
|
78
81
|
expect(() => parseSkillFile("No frontmatter here")).toThrow(
|
|
79
|
-
"SKILL.md must start with YAML frontmatter"
|
|
82
|
+
"SKILL.md must start with YAML frontmatter"
|
|
80
83
|
);
|
|
81
84
|
});
|
|
82
85
|
|
|
@@ -87,7 +90,7 @@ description: Missing name
|
|
|
87
90
|
Body`;
|
|
88
91
|
|
|
89
92
|
expect(() => parseSkillFile(raw)).toThrow(
|
|
90
|
-
"SKILL.md frontmatter must include a 'name' field"
|
|
93
|
+
"SKILL.md frontmatter must include a 'name' field"
|
|
91
94
|
);
|
|
92
95
|
});
|
|
93
96
|
|
|
@@ -98,7 +101,7 @@ name: no-desc
|
|
|
98
101
|
Body`;
|
|
99
102
|
|
|
100
103
|
expect(() => parseSkillFile(raw)).toThrow(
|
|
101
|
-
"SKILL.md frontmatter must include a 'description' field"
|
|
104
|
+
"SKILL.md frontmatter must include a 'description' field"
|
|
102
105
|
);
|
|
103
106
|
});
|
|
104
107
|
|
|
@@ -114,7 +117,8 @@ description: No body content
|
|
|
114
117
|
});
|
|
115
118
|
|
|
116
119
|
it("handles CRLF line endings", () => {
|
|
117
|
-
const raw =
|
|
120
|
+
const raw =
|
|
121
|
+
"---\r\nname: crlf-skill\r\ndescription: CRLF test\r\n---\r\nBody with CRLF";
|
|
118
122
|
|
|
119
123
|
const { frontmatter, body } = parseSkillFile(raw);
|
|
120
124
|
expect(frontmatter.name).toBe("crlf-skill");
|
|
@@ -204,7 +208,7 @@ describe("createReadSkillTool", () => {
|
|
|
204
208
|
|
|
205
209
|
it("throws when no skills are provided", () => {
|
|
206
210
|
expect(() => createReadSkillTool([])).toThrow(
|
|
207
|
-
"createReadSkillTool requires at least one skill"
|
|
211
|
+
"createReadSkillTool requires at least one skill"
|
|
208
212
|
);
|
|
209
213
|
});
|
|
210
214
|
});
|
|
@@ -242,7 +246,9 @@ describe("createReadSkillHandler", () => {
|
|
|
242
246
|
|
|
243
247
|
const text = result.toolResponse as string;
|
|
244
248
|
expect(text).toContain("Skill directory: /skills/skill-a");
|
|
245
|
-
expect(text).toContain(
|
|
249
|
+
expect(text).toContain(
|
|
250
|
+
"Relative paths in this skill resolve against the skill directory above."
|
|
251
|
+
);
|
|
246
252
|
});
|
|
247
253
|
|
|
248
254
|
it("lists resources derived from resourceContents keys", () => {
|
|
@@ -298,17 +304,21 @@ describe("createReadSkillHandler", () => {
|
|
|
298
304
|
const result = handler({ skill_name: "nonexistent" });
|
|
299
305
|
|
|
300
306
|
expect(typeof result.toolResponse).toBe("string");
|
|
301
|
-
expect(
|
|
307
|
+
expect(result.toolResponse as string).toContain("not found");
|
|
302
308
|
expect(result.data).toBeNull();
|
|
303
309
|
});
|
|
304
310
|
|
|
305
311
|
it("handles single skill", () => {
|
|
306
312
|
const skills: Skill[] = [
|
|
307
|
-
{
|
|
313
|
+
{
|
|
314
|
+
name: "skill-a",
|
|
315
|
+
description: "First",
|
|
316
|
+
instructions: "Instructions for A",
|
|
317
|
+
},
|
|
308
318
|
];
|
|
309
319
|
const handler = createReadSkillHandler(skills);
|
|
310
320
|
const result = handler({ skill_name: "skill-a" });
|
|
311
|
-
expect(
|
|
321
|
+
expect(result.toolResponse as string).toContain("Instructions for A");
|
|
312
322
|
});
|
|
313
323
|
});
|
|
314
324
|
|
|
@@ -350,7 +360,9 @@ describe("buildSkillRegistration", () => {
|
|
|
350
360
|
{ name: "dupe", description: "First", instructions: "A" },
|
|
351
361
|
{ name: "dupe", description: "Second", instructions: "B" },
|
|
352
362
|
];
|
|
353
|
-
expect(() => buildSkillRegistration(skills)).toThrow(
|
|
363
|
+
expect(() => buildSkillRegistration(skills)).toThrow(
|
|
364
|
+
"Duplicate skill names: dupe"
|
|
365
|
+
);
|
|
354
366
|
});
|
|
355
367
|
|
|
356
368
|
it("returns a complete tool entry with handler", () => {
|
|
@@ -389,7 +401,7 @@ describe("buildSkillRegistration", () => {
|
|
|
389
401
|
if (!registration) return;
|
|
390
402
|
const result = registration.handler(
|
|
391
403
|
{ skill_name: "test-skill" },
|
|
392
|
-
{ threadId: "t-1", toolCallId: "tc-1", toolName: "ReadSkill" }
|
|
404
|
+
{ threadId: "t-1", toolCallId: "tc-1", toolName: "ReadSkill" }
|
|
393
405
|
);
|
|
394
406
|
|
|
395
407
|
if (result instanceof Promise) {
|
|
@@ -414,9 +426,7 @@ describe("buildSkillRegistration", () => {
|
|
|
414
426
|
// FileSystemSkillProvider — resource discovery
|
|
415
427
|
// ---------------------------------------------------------------------------
|
|
416
428
|
|
|
417
|
-
function createMockFs(
|
|
418
|
-
tree: Record<string, string | "DIR">,
|
|
419
|
-
): SandboxFileSystem {
|
|
429
|
+
function createMockFs(tree: Record<string, string | "DIR">): SandboxFileSystem {
|
|
420
430
|
const dir = (entries: DirentEntry[]): DirentEntry[] => entries;
|
|
421
431
|
|
|
422
432
|
return {
|
|
@@ -11,7 +11,13 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
11
11
|
setHandler: (_def: unknown, _handler: unknown) => {},
|
|
12
12
|
uuid4: () =>
|
|
13
13
|
`00000000-0000-0000-0000-${String(++idCounter).padStart(12, "0")}`,
|
|
14
|
-
log: {
|
|
14
|
+
log: {
|
|
15
|
+
trace: () => {},
|
|
16
|
+
debug: () => {},
|
|
17
|
+
info: () => {},
|
|
18
|
+
warn: () => {},
|
|
19
|
+
error: () => {},
|
|
20
|
+
},
|
|
15
21
|
};
|
|
16
22
|
});
|
|
17
23
|
|
|
@@ -148,7 +154,11 @@ describe("createAgentStateManager integration", () => {
|
|
|
148
154
|
const sm = createAgentStateManager({});
|
|
149
155
|
|
|
150
156
|
sm.updateUsage({ inputTokens: 100, outputTokens: 50 });
|
|
151
|
-
sm.updateUsage({
|
|
157
|
+
sm.updateUsage({
|
|
158
|
+
inputTokens: 200,
|
|
159
|
+
outputTokens: 100,
|
|
160
|
+
cachedWriteTokens: 30,
|
|
161
|
+
});
|
|
152
162
|
sm.updateUsage({ cachedReadTokens: 20, reasonTokens: 10 });
|
|
153
163
|
|
|
154
164
|
expect(sm.getTotalUsage()).toEqual({
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
startChild,
|
|
3
2
|
workflowInfo,
|
|
4
3
|
setHandler,
|
|
5
4
|
condition,
|
|
6
5
|
log,
|
|
6
|
+
ApplicationFailure,
|
|
7
|
+
executeChild,
|
|
7
8
|
} from "@temporalio/workflow";
|
|
8
9
|
import { getShortId } from "../thread/id";
|
|
9
10
|
import type { ToolHandlerResponse, RouterContext } from "../tool-router";
|
|
@@ -11,7 +12,6 @@ import type { JsonValue } from "../state/types";
|
|
|
11
12
|
import type {
|
|
12
13
|
InferSubagentResult,
|
|
13
14
|
SubagentConfig,
|
|
14
|
-
SubagentHandlerResponse,
|
|
15
15
|
SubagentSandboxConfig,
|
|
16
16
|
SubagentWorkflowInput,
|
|
17
17
|
} from "./types";
|
|
@@ -22,17 +22,14 @@ import type {
|
|
|
22
22
|
SandboxInit,
|
|
23
23
|
SubagentSandboxShutdown,
|
|
24
24
|
} from "../lifecycle";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
childSandboxReadySignal,
|
|
28
|
-
destroySandboxSignal,
|
|
29
|
-
} from "./signals";
|
|
25
|
+
import type { SandboxOps, SandboxSnapshot } from "../sandbox/types";
|
|
26
|
+
import { childSandboxReadySignal } from "./signals";
|
|
30
27
|
|
|
31
28
|
/** Normalized sandbox config after resolving the union. */
|
|
32
29
|
interface ResolvedSandboxConfig {
|
|
33
30
|
source: "none" | "inherit" | "own";
|
|
34
31
|
init: "per-call" | "once";
|
|
35
|
-
continuation: "continue" | "fork";
|
|
32
|
+
continuation: "continue" | "fork" | "snapshot";
|
|
36
33
|
shutdown?: SubagentSandboxShutdown;
|
|
37
34
|
}
|
|
38
35
|
|
|
@@ -61,9 +58,9 @@ function resolveSandboxConfig(
|
|
|
61
58
|
/**
|
|
62
59
|
* Creates a Subagent tool handler that spawns child workflows for configured subagents.
|
|
63
60
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
61
|
+
* Sandbox and snapshot cleanup happens inside the parent via each subagent's
|
|
62
|
+
* `sandbox.proxy` — the proxy factory is invoked once per subagent with
|
|
63
|
+
* `scope = agentName` so it resolves to the same activities the child uses.
|
|
67
64
|
*
|
|
68
65
|
* @param subagents - Array of subagent configurations
|
|
69
66
|
* @returns A tool handler function that can be used with the tool router
|
|
@@ -78,13 +75,27 @@ export function createSubagentHandler<
|
|
|
78
75
|
context: RouterContext
|
|
79
76
|
) => Promise<ToolHandlerResponse<InferSubagentResult<T[number]> | null>>;
|
|
80
77
|
destroySubagentSandboxes: () => Promise<void>;
|
|
78
|
+
cleanupSubagentSnapshots: () => Promise<void>;
|
|
81
79
|
} {
|
|
82
80
|
const { taskQueue: parentTaskQueue } = workflowInfo();
|
|
83
81
|
|
|
84
|
-
|
|
82
|
+
/** Sandbox ops proxy per subagent, built eagerly from `sandbox.proxy` factories. */
|
|
83
|
+
const agentSandboxOps = new Map<string, SandboxOps>();
|
|
84
|
+
for (const cfg of subagents) {
|
|
85
|
+
if (cfg.sandbox && cfg.sandbox !== "none") {
|
|
86
|
+
agentSandboxOps.set(cfg.agentName, cfg.sandbox.proxy(cfg.agentName));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sandboxes that outlived their child session and must be destroyed by the
|
|
92
|
+
* parent at shutdown (shutdown = `pause-until-parent-close` /
|
|
93
|
+
* `keep-until-parent-close`). Keyed by `persistent:<agent>` for lazy
|
|
94
|
+
* shared sandboxes and by childWorkflowId otherwise.
|
|
95
|
+
*/
|
|
85
96
|
const pendingDestroys = new Map<
|
|
86
97
|
string,
|
|
87
|
-
|
|
98
|
+
{ agentName: string; sandboxId: string }
|
|
88
99
|
>();
|
|
89
100
|
/** Maps childThreadId → sandboxId for sandbox continuation across invocations (init: per-call) */
|
|
90
101
|
const threadSandboxes = new Map<string, string>();
|
|
@@ -94,21 +105,26 @@ export function createSubagentHandler<
|
|
|
94
105
|
const persistentSandboxCreating = new Set<string>();
|
|
95
106
|
/** Reverse lookup: childWorkflowId → agentName for in-flight lazy creators */
|
|
96
107
|
const lazyCreatorAgent = new Map<string, string>();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
childSandboxReadySignal,
|
|
104
|
-
({ childWorkflowId, sandboxId }) => {
|
|
105
|
-
const agentName = lazyCreatorAgent.get(childWorkflowId);
|
|
106
|
-
if (agentName && !persistentSandboxes.has(agentName)) {
|
|
107
|
-
persistentSandboxes.set(agentName, sandboxId);
|
|
108
|
-
lazyCreatorAgent.delete(childWorkflowId);
|
|
109
|
-
}
|
|
108
|
+
/** Maps childThreadId → latest snapshot for sandbox continuation via snapshots */
|
|
109
|
+
const threadSnapshots = new Map<
|
|
110
|
+
string,
|
|
111
|
+
{
|
|
112
|
+
agentName: string;
|
|
113
|
+
snapshot: SandboxSnapshot;
|
|
110
114
|
}
|
|
111
|
-
);
|
|
115
|
+
>();
|
|
116
|
+
/** Maps agentName → reusable base snapshot captured on first-ever call (init: once + continuation: "snapshot") */
|
|
117
|
+
const persistentBaseSnapshot = new Map<string, SandboxSnapshot>();
|
|
118
|
+
/** Tracks agents whose first snapshot-backed sandbox creation is in-flight */
|
|
119
|
+
const persistentBaseSnapshotCreating = new Set<string>();
|
|
120
|
+
|
|
121
|
+
setHandler(childSandboxReadySignal, ({ childWorkflowId, sandboxId }) => {
|
|
122
|
+
const agentName = lazyCreatorAgent.get(childWorkflowId);
|
|
123
|
+
if (agentName && !persistentSandboxes.has(agentName)) {
|
|
124
|
+
persistentSandboxes.set(agentName, sandboxId);
|
|
125
|
+
lazyCreatorAgent.delete(childWorkflowId);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
112
128
|
|
|
113
129
|
const handler = async (
|
|
114
130
|
args: SubagentArgs,
|
|
@@ -127,6 +143,16 @@ export function createSubagentHandler<
|
|
|
127
143
|
const { sandboxId: parentSandboxId } = context;
|
|
128
144
|
const sandboxCfg = resolveSandboxConfig(config.sandbox);
|
|
129
145
|
|
|
146
|
+
if (
|
|
147
|
+
sandboxCfg.source !== "none" &&
|
|
148
|
+
!agentSandboxOps.has(config.agentName)
|
|
149
|
+
) {
|
|
150
|
+
throw ApplicationFailure.create({
|
|
151
|
+
message: `Subagent "${config.agentName}" uses a sandbox but no \`sandbox.proxy\` is configured on its SubagentConfig`,
|
|
152
|
+
nonRetryable: true,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
if (sandboxCfg.source === "inherit" && !parentSandboxId) {
|
|
131
157
|
throw new Error(
|
|
132
158
|
`Subagent "${config.agentName}" is configured with sandbox: "inherit" but the parent has no sandbox`
|
|
@@ -151,13 +177,50 @@ export function createSubagentHandler<
|
|
|
151
177
|
let sandbox: SandboxInit | undefined;
|
|
152
178
|
let sandboxShutdownOverride: SubagentSandboxShutdown | undefined;
|
|
153
179
|
let isLazyCreator = false;
|
|
180
|
+
let isSnapshotBaseCreator = false;
|
|
154
181
|
|
|
155
182
|
if (sandboxCfg.source === "inherit" && parentSandboxId) {
|
|
156
183
|
if (sandboxCfg.continuation === "fork") {
|
|
157
184
|
sandbox = { mode: "fork", sandboxId: parentSandboxId };
|
|
185
|
+
} else if (sandboxCfg.continuation === "snapshot") {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Subagent "${config.agentName}" has sandbox source "inherit" with continuation "snapshot" — snapshot continuation is only supported for source "own"`
|
|
188
|
+
);
|
|
158
189
|
} else {
|
|
159
190
|
sandbox = { mode: "inherit", sandboxId: parentSandboxId };
|
|
160
191
|
}
|
|
192
|
+
} else if (
|
|
193
|
+
sandboxCfg.source === "own" &&
|
|
194
|
+
sandboxCfg.continuation === "snapshot"
|
|
195
|
+
) {
|
|
196
|
+
// Snapshot-driven continuation: each call boots a fresh sandbox from a
|
|
197
|
+
// stored snapshot (per-thread, or a per-agent base for new threads with
|
|
198
|
+
// init: "once"). The session destroys its sandbox inline on exit;
|
|
199
|
+
// stored snapshot IDs are cleaned up by the parent at shutdown.
|
|
200
|
+
const isLazy = sandboxCfg.init === "once";
|
|
201
|
+
|
|
202
|
+
let baseSnap: SandboxSnapshot | undefined;
|
|
203
|
+
if (continuationThreadId) {
|
|
204
|
+
baseSnap = threadSnapshots.get(continuationThreadId)?.snapshot;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!baseSnap && isLazy) {
|
|
208
|
+
baseSnap = persistentBaseSnapshot.get(config.agentName);
|
|
209
|
+
if (!baseSnap) {
|
|
210
|
+
if (persistentBaseSnapshotCreating.has(config.agentName)) {
|
|
211
|
+
await condition(() => persistentBaseSnapshot.has(config.agentName));
|
|
212
|
+
baseSnap = persistentBaseSnapshot.get(config.agentName);
|
|
213
|
+
} else {
|
|
214
|
+
persistentBaseSnapshotCreating.add(config.agentName);
|
|
215
|
+
isSnapshotBaseCreator = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (baseSnap) {
|
|
221
|
+
sandbox = { mode: "from-snapshot", snapshot: baseSnap };
|
|
222
|
+
}
|
|
223
|
+
sandboxShutdownOverride = "snapshot";
|
|
161
224
|
} else if (sandboxCfg.source === "own") {
|
|
162
225
|
const isLazy = sandboxCfg.init === "once";
|
|
163
226
|
|
|
@@ -246,45 +309,11 @@ export function createSubagentHandler<
|
|
|
246
309
|
sandboxSource: sandboxCfg.source,
|
|
247
310
|
});
|
|
248
311
|
|
|
249
|
-
const
|
|
312
|
+
const childResult = await executeChild(config.workflow, childOpts);
|
|
250
313
|
|
|
251
|
-
// Track child handles that need signaling at parent shutdown.
|
|
252
314
|
const effectiveShutdown =
|
|
253
315
|
sandboxShutdownOverride ?? sandboxCfg.shutdown ?? "destroy";
|
|
254
316
|
|
|
255
|
-
if (
|
|
256
|
-
effectiveShutdown === "pause-until-parent-close" ||
|
|
257
|
-
effectiveShutdown === "keep-until-parent-close"
|
|
258
|
-
) {
|
|
259
|
-
const key = isLazyCreator
|
|
260
|
-
? `persistent:${config.agentName}`
|
|
261
|
-
: childWorkflowId;
|
|
262
|
-
pendingDestroys.set(key, childHandle);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Wait for signal from child; race with child completion to propagate failures
|
|
266
|
-
await Promise.race([
|
|
267
|
-
condition(() => childResults.has(childWorkflowId)),
|
|
268
|
-
childHandle.result(),
|
|
269
|
-
]);
|
|
270
|
-
if (!childResults.has(childWorkflowId)) {
|
|
271
|
-
await condition(() => childResults.has(childWorkflowId));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const childResult = childResults.get(childWorkflowId);
|
|
275
|
-
childResults.delete(childWorkflowId);
|
|
276
|
-
|
|
277
|
-
if (!childResult) {
|
|
278
|
-
log.warn("subagent returned no result", {
|
|
279
|
-
subagent: config.agentName,
|
|
280
|
-
childWorkflowId,
|
|
281
|
-
});
|
|
282
|
-
return {
|
|
283
|
-
toolResponse: "Subagent workflow did not signal a result",
|
|
284
|
-
data: null,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
317
|
log.info("subagent completed", {
|
|
289
318
|
subagent: config.agentName,
|
|
290
319
|
childWorkflowId,
|
|
@@ -297,27 +326,71 @@ export function createSubagentHandler<
|
|
|
297
326
|
usage,
|
|
298
327
|
threadId: childThreadId,
|
|
299
328
|
sandboxId: childSandboxId,
|
|
329
|
+
snapshot: childSnapshot,
|
|
330
|
+
baseSnapshot: childBaseSnapshot,
|
|
300
331
|
metadata,
|
|
301
332
|
} = childResult;
|
|
302
333
|
|
|
303
|
-
// Store sandbox ID for future continuation/fork
|
|
304
334
|
if (childSandboxId) {
|
|
305
335
|
if (
|
|
306
336
|
sandboxCfg.source === "own" &&
|
|
307
337
|
sandboxCfg.init === "once" &&
|
|
338
|
+
sandboxCfg.continuation !== "snapshot" &&
|
|
308
339
|
!persistentSandboxes.has(config.agentName)
|
|
309
340
|
) {
|
|
310
341
|
// Fallback: signal may have already set this via childSandboxReadySignal
|
|
311
342
|
persistentSandboxes.set(config.agentName, childSandboxId);
|
|
312
|
-
} else if (
|
|
343
|
+
} else if (
|
|
344
|
+
allowsContinuation &&
|
|
345
|
+
childThreadId &&
|
|
346
|
+
sandboxCfg.source === "own" &&
|
|
347
|
+
sandboxCfg.continuation !== "snapshot"
|
|
348
|
+
) {
|
|
313
349
|
threadSandboxes.set(childThreadId, childSandboxId);
|
|
314
350
|
}
|
|
315
351
|
}
|
|
316
352
|
|
|
353
|
+
// Track sandboxes that must be destroyed by the parent at shutdown.
|
|
354
|
+
if (
|
|
355
|
+
childSandboxId &&
|
|
356
|
+
(effectiveShutdown === "pause-until-parent-close" ||
|
|
357
|
+
effectiveShutdown === "keep-until-parent-close")
|
|
358
|
+
) {
|
|
359
|
+
const key = isLazyCreator
|
|
360
|
+
? `persistent:${config.agentName}`
|
|
361
|
+
: childWorkflowId;
|
|
362
|
+
pendingDestroys.set(key, {
|
|
363
|
+
agentName: config.agentName,
|
|
364
|
+
sandboxId: childSandboxId,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Store snapshots for future snapshot-driven continuation and final sweep.
|
|
369
|
+
// Tag each with `agentName` so `cleanupSubagentSnapshots` knows which
|
|
370
|
+
// sandbox ops to call for deletion.
|
|
371
|
+
if (sandboxCfg.source === "own" && sandboxCfg.continuation === "snapshot") {
|
|
372
|
+
if (childSnapshot && childThreadId) {
|
|
373
|
+
threadSnapshots.set(childThreadId, {
|
|
374
|
+
agentName: config.agentName,
|
|
375
|
+
snapshot: childSnapshot,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
if (
|
|
379
|
+
isSnapshotBaseCreator &&
|
|
380
|
+
childBaseSnapshot &&
|
|
381
|
+
!persistentBaseSnapshot.has(config.agentName)
|
|
382
|
+
) {
|
|
383
|
+
persistentBaseSnapshot.set(config.agentName, childBaseSnapshot);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
317
387
|
if (isLazyCreator) {
|
|
318
388
|
persistentSandboxCreating.delete(config.agentName);
|
|
319
389
|
lazyCreatorAgent.delete(childWorkflowId);
|
|
320
390
|
}
|
|
391
|
+
if (isSnapshotBaseCreator) {
|
|
392
|
+
persistentBaseSnapshotCreating.delete(config.agentName);
|
|
393
|
+
}
|
|
321
394
|
|
|
322
395
|
if (!toolResponse) {
|
|
323
396
|
return {
|
|
@@ -355,7 +428,9 @@ export function createSubagentHandler<
|
|
|
355
428
|
|
|
356
429
|
return {
|
|
357
430
|
toolResponse: finalToolResponse,
|
|
358
|
-
data: validated
|
|
431
|
+
data: validated
|
|
432
|
+
? validated.data
|
|
433
|
+
: (data as InferSubagentResult<T[number]> | null),
|
|
359
434
|
...(usage && { usage }),
|
|
360
435
|
...(childSandboxId && { sandboxId: childSandboxId }),
|
|
361
436
|
...(metadata && { metadata }),
|
|
@@ -363,15 +438,55 @@ export function createSubagentHandler<
|
|
|
363
438
|
};
|
|
364
439
|
|
|
365
440
|
const destroySubagentSandboxes = async (): Promise<void> => {
|
|
366
|
-
const
|
|
441
|
+
const entries = [...pendingDestroys.values()];
|
|
367
442
|
pendingDestroys.clear();
|
|
368
443
|
await Promise.all(
|
|
369
|
-
|
|
444
|
+
entries.map(async ({ agentName, sandboxId }) => {
|
|
445
|
+
const ops = agentSandboxOps.get(agentName);
|
|
446
|
+
if (!ops) {
|
|
447
|
+
log.warn(
|
|
448
|
+
"Skipping sandbox destroy — no sandbox.proxy registered for agent",
|
|
449
|
+
{ agentName, sandboxId }
|
|
450
|
+
);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
await ops.destroySandbox(sandboxId);
|
|
455
|
+
} catch (err) {
|
|
456
|
+
log.warn("Failed to destroy subagent sandbox", {
|
|
457
|
+
agentName,
|
|
458
|
+
sandboxId,
|
|
459
|
+
error: err,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const cleanupSubagentSnapshots = async (): Promise<void> => {
|
|
467
|
+
const tagged = [];
|
|
468
|
+
for (const entry of threadSnapshots.values()) tagged.push(entry);
|
|
469
|
+
for (const [agentName, snapshot] of persistentBaseSnapshot.entries()) {
|
|
470
|
+
tagged.push({ agentName, snapshot });
|
|
471
|
+
}
|
|
472
|
+
threadSnapshots.clear();
|
|
473
|
+
persistentBaseSnapshot.clear();
|
|
474
|
+
|
|
475
|
+
await Promise.all(
|
|
476
|
+
tagged.map(async ({ agentName, snapshot }) => {
|
|
477
|
+
const ops = agentSandboxOps.get(agentName);
|
|
478
|
+
if (!ops) {
|
|
479
|
+
log.warn(
|
|
480
|
+
"Skipping snapshot delete — no sandbox.proxy registered for agent",
|
|
481
|
+
{ agentName }
|
|
482
|
+
);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
370
485
|
try {
|
|
371
|
-
await
|
|
372
|
-
await handle.result();
|
|
486
|
+
await ops.deleteSandboxSnapshot(snapshot);
|
|
373
487
|
} catch (err) {
|
|
374
|
-
log.warn("Failed to
|
|
488
|
+
log.warn("Failed to delete subagent snapshot", {
|
|
489
|
+
agentName,
|
|
375
490
|
error: err,
|
|
376
491
|
});
|
|
377
492
|
}
|
|
@@ -379,5 +494,5 @@ export function createSubagentHandler<
|
|
|
379
494
|
);
|
|
380
495
|
};
|
|
381
496
|
|
|
382
|
-
return { handler, destroySubagentSandboxes };
|
|
497
|
+
return { handler, destroySubagentSandboxes, cleanupSubagentSnapshots };
|
|
383
498
|
}
|
|
@@ -17,8 +17,4 @@ export { createSubagentHandler } from "./handler";
|
|
|
17
17
|
export { defineSubagent } from "./define";
|
|
18
18
|
export { defineSubagentWorkflow } from "./workflow";
|
|
19
19
|
export { buildSubagentRegistration } from "./register";
|
|
20
|
-
export {
|
|
21
|
-
childResultSignal,
|
|
22
|
-
childSandboxReadySignal,
|
|
23
|
-
destroySandboxSignal,
|
|
24
|
-
} from "./signals";
|
|
20
|
+
export { childSandboxReadySignal } from "./signals";
|
|
@@ -26,6 +26,7 @@ import { createSubagentHandler } from "./handler";
|
|
|
26
26
|
export function buildSubagentRegistration(subagents: SubagentConfig[]): {
|
|
27
27
|
registration: ToolMap[string];
|
|
28
28
|
destroySubagentSandboxes: () => Promise<void>;
|
|
29
|
+
cleanupSubagentSnapshots: () => Promise<void>;
|
|
29
30
|
} | null {
|
|
30
31
|
if (subagents.length === 0) return null;
|
|
31
32
|
|
|
@@ -42,7 +43,7 @@ export function buildSubagentRegistration(subagents: SubagentConfig[]): {
|
|
|
42
43
|
const resolveSubagentName = (args: unknown): string =>
|
|
43
44
|
(args as SubagentArgs).subagent;
|
|
44
45
|
|
|
45
|
-
const { handler, destroySubagentSandboxes } =
|
|
46
|
+
const { handler, destroySubagentSandboxes, cleanupSubagentSnapshots } =
|
|
46
47
|
createSubagentHandler(subagents);
|
|
47
48
|
|
|
48
49
|
const registration: ToolMap[string] = {
|
|
@@ -72,5 +73,5 @@ export function buildSubagentRegistration(subagents: SubagentConfig[]): {
|
|
|
72
73
|
}),
|
|
73
74
|
};
|
|
74
75
|
|
|
75
|
-
return { registration, destroySubagentSandboxes };
|
|
76
|
+
return { registration, destroySubagentSandboxes, cleanupSubagentSnapshots };
|
|
76
77
|
}
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { defineSignal } from "@temporalio/workflow";
|
|
2
|
-
import type {
|
|
3
|
-
ChildResultSignalPayload,
|
|
4
|
-
ChildSandboxReadySignalPayload,
|
|
5
|
-
} from "./types";
|
|
6
|
-
|
|
7
|
-
export const childResultSignal =
|
|
8
|
-
defineSignal<[ChildResultSignalPayload]>("childResult");
|
|
2
|
+
import type { ChildSandboxReadySignalPayload } from "./types";
|
|
9
3
|
|
|
10
4
|
/** Sent by a child workflow as soon as its sandbox is created, before the agent loop starts. */
|
|
11
5
|
export const childSandboxReadySignal =
|
|
12
6
|
defineSignal<[ChildSandboxReadySignalPayload]>("childSandboxReady");
|
|
13
|
-
|
|
14
|
-
/** Sent by the parent to tell a subagent it may destroy its sandbox. */
|
|
15
|
-
export const destroySandboxSignal = defineSignal("destroySandbox");
|