zeitlich 0.2.2 → 0.2.4

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.
Files changed (46) hide show
  1. package/README.md +34 -31
  2. package/dist/index.cjs +330 -399
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +24 -43
  5. package/dist/index.d.ts +24 -43
  6. package/dist/index.js +301 -373
  7. package/dist/index.js.map +1 -1
  8. package/dist/{workflow-BQf5EfNN.d.cts → workflow-PjeURKw4.d.cts} +265 -258
  9. package/dist/{workflow-BQf5EfNN.d.ts → workflow-PjeURKw4.d.ts} +265 -258
  10. package/dist/workflow.cjs +223 -281
  11. package/dist/workflow.cjs.map +1 -1
  12. package/dist/workflow.d.cts +2 -3
  13. package/dist/workflow.d.ts +2 -3
  14. package/dist/workflow.js +198 -258
  15. package/dist/workflow.js.map +1 -1
  16. package/package.json +3 -2
  17. package/src/activities.ts +0 -32
  18. package/src/index.ts +14 -11
  19. package/src/lib/model-invoker.ts +7 -1
  20. package/src/lib/session.ts +54 -109
  21. package/src/lib/thread-manager.ts +45 -37
  22. package/src/lib/tool-router.ts +148 -108
  23. package/src/lib/types.ts +35 -26
  24. package/src/tools/ask-user-question/handler.ts +5 -5
  25. package/src/tools/ask-user-question/tool.ts +3 -2
  26. package/src/tools/bash/bash.test.ts +12 -12
  27. package/src/tools/bash/handler.ts +5 -5
  28. package/src/tools/bash/tool.ts +3 -2
  29. package/src/tools/edit/handler.ts +78 -123
  30. package/src/tools/edit/tool.ts +3 -2
  31. package/src/tools/glob/handler.ts +17 -48
  32. package/src/tools/glob/tool.ts +3 -2
  33. package/src/tools/grep/tool.ts +3 -2
  34. package/src/tools/{read → read-file}/tool.ts +3 -2
  35. package/src/tools/{task → subagent}/handler.ts +18 -31
  36. package/src/tools/{task → subagent}/tool.ts +13 -20
  37. package/src/tools/task-create/handler.ts +5 -11
  38. package/src/tools/task-create/tool.ts +3 -2
  39. package/src/tools/task-get/handler.ts +5 -10
  40. package/src/tools/task-get/tool.ts +3 -2
  41. package/src/tools/task-list/handler.ts +5 -10
  42. package/src/tools/task-list/tool.ts +3 -2
  43. package/src/tools/task-update/handler.ts +5 -12
  44. package/src/tools/task-update/tool.ts +3 -2
  45. package/src/tools/{write → write-file}/tool.ts +5 -6
  46. package/src/workflow.ts +24 -21
@@ -1,7 +1,7 @@
1
1
  import { dirname } from "path";
2
2
  import { fileURLToPath } from "url";
3
3
  import { describe, expect, it } from "vitest";
4
- import { handleBashTool } from "./handler";
4
+ import { createBashHandler } from "./handler";
5
5
  import { OverlayFs } from "just-bash";
6
6
 
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -10,7 +10,7 @@ describe("bash with default options", () => {
10
10
  const fs = new OverlayFs({ root: __dirname, mountPoint: "/home/user" });
11
11
 
12
12
  it("executes echo and captures stdout", async () => {
13
- const { data } = await handleBashTool({fs})(
13
+ const { data } = await createBashHandler({fs})(
14
14
  { command: "echo 'hello world'" },
15
15
  {}
16
16
  );
@@ -20,17 +20,17 @@ describe("bash with default options", () => {
20
20
  });
21
21
 
22
22
  it("returns exit code 0 for successful commands", async () => {
23
- const { data } = await handleBashTool({fs})({ command: "true" }, {});
23
+ const { data } = await createBashHandler({fs})({ command: "true" }, {});
24
24
  expect(data?.exitCode).toBe(0);
25
25
  });
26
26
 
27
27
  it("returns non-zero exit code for failed commands", async () => {
28
- const { data } = await handleBashTool({fs})({ command: "false" }, {});
28
+ const { data } = await createBashHandler({fs})({ command: "false" }, {});
29
29
  expect(data?.exitCode).toBe(1);
30
30
  });
31
31
 
32
32
  it("captures stderr output", async () => {
33
- const { data } = await handleBashTool({fs})(
33
+ const { data } = await createBashHandler({fs})(
34
34
  { command: "echo 'error message' >&2" },
35
35
  {}
36
36
  );
@@ -39,7 +39,7 @@ describe("bash with default options", () => {
39
39
  });
40
40
 
41
41
  it("supports piping between commands", async () => {
42
- const { data } = await handleBashTool({fs})(
42
+ const { data } = await createBashHandler({fs})(
43
43
  { command: "echo 'hello world' | tr 'a-z' 'A-Z'" },
44
44
  {}
45
45
  );
@@ -47,7 +47,7 @@ describe("bash with default options", () => {
47
47
  });
48
48
 
49
49
  it("supports command chaining with &&", async () => {
50
- const { data } = await handleBashTool({fs})(
50
+ const { data } = await createBashHandler({fs})(
51
51
  { command: "echo 'first' && echo 'second'" },
52
52
  {}
53
53
  );
@@ -56,7 +56,7 @@ describe("bash with default options", () => {
56
56
  });
57
57
 
58
58
  it("handles multi-line output", async () => {
59
- const { data } = await handleBashTool({fs})(
59
+ const { data } = await createBashHandler({fs})(
60
60
  { command: "printf 'line1\\nline2\\nline3'" },
61
61
  {}
62
62
  );
@@ -67,7 +67,7 @@ describe("bash with default options", () => {
67
67
  });
68
68
 
69
69
  it("handles commands with arguments and flags", async () => {
70
- const { data } = await handleBashTool({fs})(
70
+ const { data } = await createBashHandler({fs})(
71
71
  { command: "echo -n 'no newline'" },
72
72
  {}
73
73
  );
@@ -75,7 +75,7 @@ describe("bash with default options", () => {
75
75
  });
76
76
 
77
77
  it("supports command substitution", async () => {
78
- const { data } = await handleBashTool({fs})(
78
+ const { data } = await createBashHandler({fs})(
79
79
  { command: "echo \"count: $(echo 'a b c' | wc -w | tr -d ' ')\"" },
80
80
  {}
81
81
  );
@@ -83,7 +83,7 @@ describe("bash with default options", () => {
83
83
  });
84
84
 
85
85
  it("returns toolResponse string with formatted output", async () => {
86
- const { toolResponse } = await handleBashTool({fs})(
86
+ const { toolResponse } = await createBashHandler({fs})(
87
87
  { command: "echo 'test'" },
88
88
  {}
89
89
  );
@@ -96,7 +96,7 @@ describe("bash with default options", () => {
96
96
  describe("bash with overlay filesystem", () => {
97
97
  it("sees files in the current directory", async () => {
98
98
  const fs = new OverlayFs({ root: __dirname, mountPoint: "/home/user" });
99
- const { data } = await handleBashTool({fs})({ command: "ls" }, {});
99
+ const { data } = await createBashHandler({fs})({ command: "ls" }, {});
100
100
  expect(data?.stdout).toContain("bash.test.ts");
101
101
  expect(data?.stdout).toContain("handler.ts");
102
102
  expect(data?.stdout).toContain("tool.ts");
@@ -1,5 +1,5 @@
1
- import type { ActivityToolHandler } from "../../workflow";
2
- import type { bashToolSchemaType } from "./tool";
1
+ import type { ActivityToolHandler } from "../../lib/tool-router";
2
+ import type { BashArgs } from "./tool";
3
3
  import { Bash, type BashOptions } from "just-bash";
4
4
 
5
5
  type BashExecOut = {
@@ -11,10 +11,10 @@ type BashExecOut = {
11
11
  /** BashOptions with `fs` required */
12
12
  type BashToolOptions = Required<Pick<BashOptions, "fs">> & Omit<BashOptions, "fs">;
13
13
 
14
- export const handleBashTool: (
14
+ export const createBashHandler: (
15
15
  bashOptions: BashToolOptions,
16
- ) => ActivityToolHandler<bashToolSchemaType, BashExecOut | null> =
17
- (bashOptions: BashToolOptions) => async (args: bashToolSchemaType, _context) => {
16
+ ) => ActivityToolHandler<BashArgs, BashExecOut | null> =
17
+ (bashOptions: BashToolOptions) => async (args: BashArgs, _context) => {
18
18
  const { command } = args;
19
19
 
20
20
  const mergedOptions: BashOptions = {
@@ -1,4 +1,5 @@
1
1
  import z from "zod";
2
+ import type { ToolDefinition } from "../../lib/tool-router";
2
3
 
3
4
  export const createBashToolDescription = ({
4
5
  fileTree,
@@ -31,6 +32,6 @@ Use this tool to:
31
32
  ),
32
33
  }),
33
34
  strict: true,
34
- };
35
+ } satisfies ToolDefinition;
35
36
 
36
- export type bashToolSchemaType = z.infer<typeof bashTool.schema>;
37
+ export type BashArgs = z.infer<typeof bashTool.schema>;
@@ -1,38 +1,16 @@
1
- import type { EditToolSchemaType } from "./tool";
1
+ import type { ActivityToolHandler } from "../../lib/tool-router";
2
+ import type { FileEditArgs } from "./tool";
2
3
  import type { IFileSystem } from "just-bash";
3
4
 
4
5
  /**
5
6
  * Result of an edit operation
6
7
  */
7
- export interface EditResult {
8
+ interface EditResult {
8
9
  path: string;
9
10
  success: boolean;
10
11
  replacements: number;
11
12
  }
12
13
 
13
- /**
14
- * Edit handler response
15
- */
16
- export interface EditHandlerResponse {
17
- toolResponse: string;
18
- data: EditResult;
19
- }
20
-
21
- /**
22
- * Options for edit handler
23
- */
24
- export interface EditHandlerOptions {
25
- /**
26
- * Set of file paths that have been read in this session.
27
- * Required for enforcing read-before-write policy.
28
- */
29
- readFiles: Set<string>;
30
- /**
31
- * If true, skip the read-before-write check (not recommended)
32
- */
33
- skipReadCheck?: boolean;
34
- }
35
-
36
14
  /**
37
15
  * Escape special regex characters in a string
38
16
  */
@@ -41,116 +19,93 @@ function escapeRegExp(str: string): string {
41
19
  }
42
20
 
43
21
  /**
44
- * Edit handler that edits files within the scoped file tree.
22
+ * Creates an edit handler that edits files within the scoped file tree.
45
23
  *
46
- * @param args - Tool arguments (file_path, old_string, new_string, replace_all)
47
- * @param options - Additional options (readFiles, skipReadCheck)
24
+ * @param fs - File system implementation for I/O operations
25
+ * @returns An ActivityToolHandler for edit tool calls
48
26
  */
49
- export async function editHandler(
50
- args: EditToolSchemaType,
27
+ export function createEditHandler(
51
28
  fs: IFileSystem
52
- ): Promise<EditHandlerResponse> {
53
- const { file_path, old_string, new_string, replace_all = false } = args;
54
-
55
- // Validate old_string !== new_string
56
- if (old_string === new_string) {
57
- return {
58
- toolResponse: `Error: old_string and new_string must be different.`,
59
- data: {
60
- path: file_path,
61
- success: false,
62
- replacements: 0,
63
- },
64
- };
65
- }
29
+ ): ActivityToolHandler<FileEditArgs, EditResult> {
30
+ return async (args) => {
31
+ const { file_path, old_string, new_string, replace_all = false } = args;
66
32
 
67
- try {
68
- // Check if file exists
69
- const exists = await fs.exists(file_path);
70
- if (!exists) {
33
+ // Validate old_string !== new_string
34
+ if (old_string === new_string) {
71
35
  return {
72
- toolResponse: `Error: File "${file_path}" does not exist.`,
73
- data: {
74
- path: file_path,
75
- success: false,
76
- replacements: 0,
77
- },
36
+ toolResponse: `Error: old_string and new_string must be different.`,
37
+ data: { path: file_path, success: false, replacements: 0 },
78
38
  };
79
39
  }
80
40
 
81
- // Read current content
82
- const content = await fs.readFile(file_path);
41
+ try {
42
+ // Check if file exists
43
+ const exists = await fs.exists(file_path);
44
+ if (!exists) {
45
+ return {
46
+ toolResponse: `Error: File "${file_path}" does not exist.`,
47
+ data: { path: file_path, success: false, replacements: 0 },
48
+ };
49
+ }
50
+
51
+ // Read current content
52
+ const content = await fs.readFile(file_path);
53
+
54
+ // Check if old_string exists in the file
55
+ if (!content.includes(old_string)) {
56
+ return {
57
+ toolResponse: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
58
+ data: { path: file_path, success: false, replacements: 0 },
59
+ };
60
+ }
61
+
62
+ // Count occurrences
63
+ const escapedOldString = escapeRegExp(old_string);
64
+ const globalRegex = new RegExp(escapedOldString, "g");
65
+ const occurrences = (content.match(globalRegex) || []).length;
66
+
67
+ // Check uniqueness if not replace_all
68
+ if (!replace_all && occurrences > 1) {
69
+ return {
70
+ toolResponse: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
71
+ data: { path: file_path, success: false, replacements: 0 },
72
+ };
73
+ }
74
+
75
+ // Perform replacement
76
+ let newContent: string;
77
+ let replacements: number;
78
+
79
+ if (replace_all) {
80
+ newContent = content.split(old_string).join(new_string);
81
+ replacements = occurrences;
82
+ } else {
83
+ // Replace only the first occurrence
84
+ const index = content.indexOf(old_string);
85
+ newContent =
86
+ content.slice(0, index) +
87
+ new_string +
88
+ content.slice(index + old_string.length);
89
+ replacements = 1;
90
+ }
91
+
92
+ // Write the modified content
93
+ await fs.writeFile(file_path, newContent);
94
+
95
+ const summary = replace_all
96
+ ? `Replaced ${replacements} occurrence(s)`
97
+ : `Replaced 1 occurrence`;
83
98
 
84
- // Check if old_string exists in the file
85
- if (!content.includes(old_string)) {
86
99
  return {
87
- toolResponse: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
88
- data: {
89
- path: file_path,
90
- success: false,
91
- replacements: 0,
92
- },
100
+ toolResponse: `${summary} in ${file_path}`,
101
+ data: { path: file_path, success: true, replacements },
93
102
  };
94
- }
95
-
96
- // Count occurrences
97
- const escapedOldString = escapeRegExp(old_string);
98
- const globalRegex = new RegExp(escapedOldString, "g");
99
- const occurrences = (content.match(globalRegex) || []).length;
100
-
101
- // Check uniqueness if not replace_all
102
- if (!replace_all && occurrences > 1) {
103
+ } catch (error) {
104
+ const message = error instanceof Error ? error.message : "Unknown error";
103
105
  return {
104
- toolResponse: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
105
- data: {
106
- path: file_path,
107
- success: false,
108
- replacements: 0,
109
- },
106
+ toolResponse: `Error editing file "${file_path}": ${message}`,
107
+ data: { path: file_path, success: false, replacements: 0 },
110
108
  };
111
109
  }
112
-
113
- // Perform replacement
114
- let newContent: string;
115
- let replacements: number;
116
-
117
- if (replace_all) {
118
- newContent = content.split(old_string).join(new_string);
119
- replacements = occurrences;
120
- } else {
121
- // Replace only the first occurrence
122
- const index = content.indexOf(old_string);
123
- newContent =
124
- content.slice(0, index) +
125
- new_string +
126
- content.slice(index + old_string.length);
127
- replacements = 1;
128
- }
129
-
130
- // Write the modified content
131
- await fs.writeFile(file_path, newContent);
132
-
133
- const summary = replace_all
134
- ? `Replaced ${replacements} occurrence(s)`
135
- : `Replaced 1 occurrence`;
136
-
137
- return {
138
- toolResponse: `${summary} in ${file_path}`,
139
- data: {
140
- path: file_path,
141
- success: true,
142
- replacements,
143
- },
144
- };
145
- } catch (error) {
146
- const message = error instanceof Error ? error.message : "Unknown error";
147
- return {
148
- toolResponse: `Error editing file "${file_path}": ${message}`,
149
- data: {
150
- path: file_path,
151
- success: false,
152
- replacements: 0,
153
- },
154
- };
155
- }
110
+ };
156
111
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import type { ToolDefinition } from "../../lib/tool-router";
2
3
 
3
4
  export const editTool = {
4
5
  name: "FileEdit" as const,
@@ -34,6 +35,6 @@ IMPORTANT:
34
35
  ),
35
36
  }),
36
37
  strict: true,
37
- };
38
+ } satisfies ToolDefinition;
38
39
 
39
- export type EditToolSchemaType = z.infer<typeof editTool.schema>;
40
+ export type FileEditArgs = z.infer<typeof editTool.schema>;
@@ -1,62 +1,31 @@
1
+ import type { ActivityToolHandler } from "../../lib/tool-router";
2
+ import type { GlobArgs } from "./tool";
1
3
  import type { IFileSystem } from "just-bash";
2
4
  import { Bash } from "just-bash";
3
- import type { GlobToolSchemaType } from "./tool";
4
5
 
5
6
  /**
6
7
  * Result of a glob operation
7
8
  */
8
- export interface GlobResult {
9
+ interface GlobResult {
9
10
  files: string[];
10
11
  }
11
12
 
12
13
  /**
13
- * Glob handler response
14
- */
15
- export interface GlobHandlerResponse {
16
- toolResponse: string;
17
- data: GlobResult;
18
- }
19
-
20
- /**
21
- * Glob handler that searches within the scoped file tree.
14
+ * Creates a glob handler that searches within the scoped file tree.
22
15
  *
23
- * @param args - Tool arguments (pattern, root)
24
- * @param provider - FileSystemProvider for I/O operations
16
+ * @param fs - File system implementation for I/O operations
17
+ * @returns An ActivityToolHandler for glob tool calls
25
18
  */
26
- export async function globHandler(
27
- _args: GlobToolSchemaType,
19
+ export function createGlobHandler(
28
20
  fs: IFileSystem
29
- ): Promise<GlobHandlerResponse> {
30
- // const { pattern, root } = args;
31
- const _bash = new Bash({ fs });
32
-
33
- return Promise.resolve({
34
- toolResponse: "Hello, world!",
35
- data: { files: [] },
36
- });
37
-
38
- // try {
39
- // const matches = await bash.exec(`glob ${root} -name "${pattern}"`);
40
-
41
- // if (matches.length === 0) {
42
- // return {
43
- // content: `No files found matching pattern: ${pattern}`,
44
- // result: { files: [] },
45
- // };
46
- // }
47
-
48
- // const paths = matches.map((node) => node.path);
49
- // const fileList = paths.map((p) => ` ${p}`).join("\n");
50
-
51
- // return {
52
- // content: `Found ${matches.length} file(s) matching "${pattern}":\n${fileList}`,
53
- // result: { files: matches },
54
- // };
55
- // } catch (error) {
56
- // const message = error instanceof Error ? error.message : "Unknown error";
57
- // return {
58
- // content: `Error searching for files: ${message}`,
59
- // result: { files: [] },
60
- // };
61
- // }
21
+ ): ActivityToolHandler<GlobArgs, GlobResult> {
22
+ return async (_args) => {
23
+ // const { pattern, root } = args;
24
+ const _bash = new Bash({ fs });
25
+
26
+ return {
27
+ toolResponse: "Hello, world!",
28
+ data: { files: [] },
29
+ };
30
+ };
62
31
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import type { ToolDefinition } from "../../lib/tool-router";
2
3
 
3
4
  export const globTool = {
4
5
  name: "Glob" as const,
@@ -22,6 +23,6 @@ Examples:
22
23
  .describe("Optional root directory to search from"),
23
24
  }),
24
25
  strict: true,
25
- };
26
+ } satisfies ToolDefinition;
26
27
 
27
- export type GlobToolSchemaType = z.infer<typeof globTool.schema>;
28
+ export type GlobArgs = z.infer<typeof globTool.schema>;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import type { ToolDefinition } from "../../lib/tool-router";
2
3
 
3
4
  export const grepTool = {
4
5
  name: "Grep" as const,
@@ -40,6 +41,6 @@ Examples:
40
41
  .describe("Number of context lines to show around matches"),
41
42
  }),
42
43
  strict: true,
43
- };
44
+ } satisfies ToolDefinition;
44
45
 
45
- export type GrepToolSchemaType = z.infer<typeof grepTool.schema>;
46
+ export type GrepArgs = z.infer<typeof grepTool.schema>;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import type { ToolDefinition } from "../../lib/tool-router";
2
3
 
3
4
  export const readTool = {
4
5
  name: "FileRead" as const,
@@ -28,6 +29,6 @@ The tool returns the file content in an appropriate format:
28
29
  .describe("Maximum number of lines to read (for text files)"),
29
30
  }),
30
31
  strict: true,
31
- };
32
+ } satisfies ToolDefinition;
32
33
 
33
- export type ReadToolSchemaType = z.infer<typeof readTool.schema>;
34
+ export type FileReadArgs = z.infer<typeof readTool.schema>;
@@ -1,26 +1,20 @@
1
1
  import { executeChild, workflowInfo, uuid4 } from "@temporalio/workflow";
2
2
  import type { ToolHandlerResponse } from "../../lib/tool-router";
3
- import type { SubagentConfig, SubagentInput } from "../../lib/types";
4
- import type { GenericTaskToolSchemaType } from "./tool";
3
+ import type {
4
+ InferSubagentResult,
5
+ SubagentConfig,
6
+ SubagentInput,
7
+ } from "../../lib/types";
8
+ import type { SubagentArgs } from "./tool";
5
9
 
6
10
  /**
7
- * Result from a task handler execution
8
- */
9
- export interface TaskHandlerResult<TResult = unknown> {
10
- /** The validated result from the child workflow */
11
- result: TResult;
12
- /** The child workflow ID (for reference/debugging) */
13
- childWorkflowId: string;
14
- }
15
-
16
- /**
17
- * Creates a Task tool handler that spawns child workflows for configured subagents.
11
+ * Creates a Subagent tool handler that spawns child workflows for configured subagents.
18
12
  *
19
13
  * @param subagents - Array of subagent configurations
20
14
  * @returns A tool handler function that can be used with the tool router
21
15
  *
22
16
  * @example
23
- * const taskHandler = taskHandler([
17
+ * const subagentHandler = subagentHandler([
24
18
  * {
25
19
  * name: "researcher",
26
20
  * description: "Researches topics",
@@ -29,13 +23,15 @@ export interface TaskHandlerResult<TResult = unknown> {
29
23
  * },
30
24
  * ]);
31
25
  */
32
- export function createTaskHandler(subagents: SubagentConfig[]) {
26
+ export function createSubagentHandler<
27
+ const T extends readonly SubagentConfig[],
28
+ >(subagents: [...T]) {
33
29
  const { workflowId: parentWorkflowId, taskQueue: parentTaskQueue } =
34
30
  workflowInfo();
35
31
 
36
32
  return async (
37
- args: GenericTaskToolSchemaType
38
- ): Promise<ToolHandlerResponse<TaskHandlerResult>> => {
33
+ args: SubagentArgs
34
+ ): Promise<ToolHandlerResponse<InferSubagentResult<T[number]> | null>> => {
39
35
  const config = subagents.find((s) => s.name === args.subagent);
40
36
 
41
37
  if (!config) {
@@ -58,28 +54,19 @@ export function createTaskHandler(subagents: SubagentConfig[]) {
58
54
  taskQueue: config.taskQueue ?? parentTaskQueue,
59
55
  };
60
56
 
61
- const childResult =
57
+ const { toolResponse, data } =
62
58
  typeof config.workflow === "string"
63
59
  ? await executeChild(config.workflow, childOpts)
64
60
  : await executeChild(config.workflow, childOpts);
65
61
 
66
62
  // Validate result if schema provided, otherwise pass through as-is
67
- const validated = config.resultSchema
68
- ? config.resultSchema.parse(childResult)
69
- : childResult;
70
-
71
- // Format content - stringify objects, pass strings through
72
- const toolResponse =
73
- typeof validated === "string"
74
- ? validated
75
- : JSON.stringify(validated, null, 2);
63
+ const validated = (
64
+ config.resultSchema ? config.resultSchema.parse(data) : null
65
+ ) as InferSubagentResult<T[number]> | null;
76
66
 
77
67
  return {
78
68
  toolResponse,
79
- data: {
80
- result: validated,
81
- childWorkflowId,
82
- },
69
+ data: validated,
83
70
  };
84
71
  };
85
72
  }