zeitlich 0.1.1 → 0.2.0
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 +165 -180
- package/dist/index.cjs +1314 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +51 -75
- package/dist/index.js +741 -1091
- package/dist/index.js.map +1 -1
- package/dist/workflow-uVNF7zoe.d.cts +941 -0
- package/dist/workflow-uVNF7zoe.d.ts +941 -0
- package/dist/workflow.cjs +914 -0
- package/dist/workflow.cjs.map +1 -0
- package/dist/workflow.d.cts +5 -0
- package/dist/workflow.d.ts +2 -1
- package/dist/workflow.js +543 -423
- package/dist/workflow.js.map +1 -1
- package/package.json +19 -17
- package/src/activities.ts +112 -0
- package/src/index.ts +49 -0
- package/src/lib/fs.ts +80 -0
- package/src/lib/model-invoker.ts +75 -0
- package/src/lib/session.ts +216 -0
- package/src/lib/state-manager.ts +268 -0
- package/src/lib/thread-manager.ts +169 -0
- package/src/lib/tool-router.ts +717 -0
- package/src/lib/types.ts +354 -0
- package/src/plugin.ts +28 -0
- package/src/tools/ask-user-question/handler.ts +25 -0
- package/src/tools/ask-user-question/tool.ts +46 -0
- package/src/tools/bash/bash.test.ts +104 -0
- package/src/tools/bash/handler.ts +36 -0
- package/src/tools/bash/tool.ts +20 -0
- package/src/tools/edit/handler.ts +156 -0
- package/src/tools/edit/tool.ts +39 -0
- package/src/tools/glob/handler.ts +62 -0
- package/src/tools/glob/tool.ts +27 -0
- package/src/tools/grep/tool.ts +45 -0
- package/src/tools/read/tool.ts +33 -0
- package/src/tools/task/handler.ts +75 -0
- package/src/tools/task/tool.ts +96 -0
- package/src/tools/task-create/handler.ts +49 -0
- package/src/tools/task-create/tool.ts +66 -0
- package/src/tools/task-get/handler.ts +38 -0
- package/src/tools/task-get/tool.ts +11 -0
- package/src/tools/task-list/handler.ts +33 -0
- package/src/tools/task-list/tool.ts +9 -0
- package/src/tools/task-update/handler.ts +79 -0
- package/src/tools/task-update/tool.ts +20 -0
- package/src/tools/write/tool.ts +26 -0
- package/src/workflow.ts +138 -0
- package/tsup.config.ts +20 -0
- package/dist/index.d.mts +0 -152
- package/dist/index.mjs +0 -1587
- package/dist/index.mjs.map +0 -1
- package/dist/workflow-7_MT-5-w.d.mts +0 -1203
- package/dist/workflow-7_MT-5-w.d.ts +0 -1203
- package/dist/workflow.d.mts +0 -4
- package/dist/workflow.mjs +0 -739
- package/dist/workflow.mjs.map +0 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { EditToolSchemaType } from "./tool";
|
|
2
|
+
import type { IFileSystem } from "just-bash";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Result of an edit operation
|
|
6
|
+
*/
|
|
7
|
+
export interface EditResult {
|
|
8
|
+
path: string;
|
|
9
|
+
success: boolean;
|
|
10
|
+
replacements: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Edit handler response
|
|
15
|
+
*/
|
|
16
|
+
export interface EditHandlerResponse {
|
|
17
|
+
content: string;
|
|
18
|
+
result: 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
|
+
/**
|
|
37
|
+
* Escape special regex characters in a string
|
|
38
|
+
*/
|
|
39
|
+
function escapeRegExp(str: string): string {
|
|
40
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Edit handler that edits files within the scoped file tree.
|
|
45
|
+
*
|
|
46
|
+
* @param args - Tool arguments (file_path, old_string, new_string, replace_all)
|
|
47
|
+
* @param options - Additional options (readFiles, skipReadCheck)
|
|
48
|
+
*/
|
|
49
|
+
export async function editHandler(
|
|
50
|
+
args: EditToolSchemaType,
|
|
51
|
+
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
|
+
content: `Error: old_string and new_string must be different.`,
|
|
59
|
+
result: {
|
|
60
|
+
path: file_path,
|
|
61
|
+
success: false,
|
|
62
|
+
replacements: 0,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Check if file exists
|
|
69
|
+
const exists = await fs.exists(file_path);
|
|
70
|
+
if (!exists) {
|
|
71
|
+
return {
|
|
72
|
+
content: `Error: File "${file_path}" does not exist.`,
|
|
73
|
+
result: {
|
|
74
|
+
path: file_path,
|
|
75
|
+
success: false,
|
|
76
|
+
replacements: 0,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Read current content
|
|
82
|
+
const content = await fs.readFile(file_path);
|
|
83
|
+
|
|
84
|
+
// Check if old_string exists in the file
|
|
85
|
+
if (!content.includes(old_string)) {
|
|
86
|
+
return {
|
|
87
|
+
content: `Error: Could not find the specified text in "${file_path}". Make sure old_string matches exactly (whitespace-sensitive).`,
|
|
88
|
+
result: {
|
|
89
|
+
path: file_path,
|
|
90
|
+
success: false,
|
|
91
|
+
replacements: 0,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
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
|
+
return {
|
|
104
|
+
content: `Error: old_string appears ${occurrences} times in "${file_path}". Either provide more context to make it unique, or use replace_all: true.`,
|
|
105
|
+
result: {
|
|
106
|
+
path: file_path,
|
|
107
|
+
success: false,
|
|
108
|
+
replacements: 0,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
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
|
+
content: `${summary} in ${file_path}`,
|
|
139
|
+
result: {
|
|
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
|
+
content: `Error editing file "${file_path}": ${message}`,
|
|
149
|
+
result: {
|
|
150
|
+
path: file_path,
|
|
151
|
+
success: false,
|
|
152
|
+
replacements: 0,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const editTool = {
|
|
4
|
+
name: "FileEdit" as const,
|
|
5
|
+
description: `Edit specific sections of a file by replacing text.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
- Provide the exact text to find and replace
|
|
9
|
+
- The old_string must match exactly (whitespace-sensitive)
|
|
10
|
+
- By default, only replaces the first occurrence
|
|
11
|
+
- Use replace_all: true to replace all occurrences
|
|
12
|
+
|
|
13
|
+
IMPORTANT:
|
|
14
|
+
- You must read the file first (in this session) before editing it
|
|
15
|
+
- old_string must be unique in the file (unless using replace_all)
|
|
16
|
+
- The operation fails if old_string is not found
|
|
17
|
+
- old_string and new_string must be different
|
|
18
|
+
`,
|
|
19
|
+
schema: z.object({
|
|
20
|
+
file_path: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe("The absolute virtual path to the file to modify"),
|
|
23
|
+
old_string: z.string().describe("The exact text to replace"),
|
|
24
|
+
new_string: z
|
|
25
|
+
.string()
|
|
26
|
+
.describe(
|
|
27
|
+
"The text to replace it with (must be different from old_string)"
|
|
28
|
+
),
|
|
29
|
+
replace_all: z
|
|
30
|
+
.boolean()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe(
|
|
33
|
+
"If true, replace all occurrences of old_string (default: false)"
|
|
34
|
+
),
|
|
35
|
+
}),
|
|
36
|
+
strict: true,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type EditToolSchemaType = z.infer<typeof editTool.schema>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { IFileSystem } from "just-bash";
|
|
2
|
+
import { Bash } from "just-bash";
|
|
3
|
+
import type { GlobToolSchemaType } from "./tool";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Result of a glob operation
|
|
7
|
+
*/
|
|
8
|
+
export interface GlobResult {
|
|
9
|
+
files: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Glob handler response
|
|
14
|
+
*/
|
|
15
|
+
export interface GlobHandlerResponse {
|
|
16
|
+
content: string;
|
|
17
|
+
result: GlobResult;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Glob handler that searches within the scoped file tree.
|
|
22
|
+
*
|
|
23
|
+
* @param args - Tool arguments (pattern, root)
|
|
24
|
+
* @param provider - FileSystemProvider for I/O operations
|
|
25
|
+
*/
|
|
26
|
+
export async function globHandler(
|
|
27
|
+
_args: GlobToolSchemaType,
|
|
28
|
+
fs: IFileSystem
|
|
29
|
+
): Promise<GlobHandlerResponse> {
|
|
30
|
+
// const { pattern, root } = args;
|
|
31
|
+
const _bash = new Bash({ fs });
|
|
32
|
+
|
|
33
|
+
return Promise.resolve({
|
|
34
|
+
content: "Hello, world!",
|
|
35
|
+
result: { 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
|
+
// }
|
|
62
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const globTool = {
|
|
4
|
+
name: "Glob" as const,
|
|
5
|
+
description: `Search for files matching a glob pattern within the available file system.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
- Use glob patterns like "**/*.ts" to find all TypeScript files
|
|
9
|
+
- Use "docs/**" to find all files in the docs directory
|
|
10
|
+
- Patterns are matched against virtual paths in the file system
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
- "*.md" - Find all markdown files in the root
|
|
14
|
+
- "**/*.test.ts" - Find all test files recursively
|
|
15
|
+
- "src/**/*.ts" - Find all TypeScript files in src directory
|
|
16
|
+
`,
|
|
17
|
+
schema: z.object({
|
|
18
|
+
pattern: z.string().describe("Glob pattern to match files against"),
|
|
19
|
+
root: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Optional root directory to search from"),
|
|
23
|
+
}),
|
|
24
|
+
strict: true,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type GlobToolSchemaType = z.infer<typeof globTool.schema>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const grepTool = {
|
|
4
|
+
name: "Grep" as const,
|
|
5
|
+
description: `Search file contents for a pattern within the available file system.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
- Searches for a regex pattern across file contents
|
|
9
|
+
- Returns matching lines with file paths and line numbers
|
|
10
|
+
- Can filter by file patterns and limit results
|
|
11
|
+
|
|
12
|
+
Examples:
|
|
13
|
+
- Search for "TODO" in all files
|
|
14
|
+
- Search for function definitions with "function.*handleClick"
|
|
15
|
+
- Search case-insensitively with ignoreCase: true
|
|
16
|
+
`,
|
|
17
|
+
schema: z.object({
|
|
18
|
+
pattern: z
|
|
19
|
+
.string()
|
|
20
|
+
.describe("Regex pattern to search for in file contents"),
|
|
21
|
+
ignoreCase: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Case-insensitive search (default: false)"),
|
|
25
|
+
maxMatches: z
|
|
26
|
+
.number()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Maximum number of matches to return (default: 50)"),
|
|
29
|
+
includePatterns: z
|
|
30
|
+
.array(z.string())
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Glob patterns to include (e.g., ['*.ts', '*.js'])"),
|
|
33
|
+
excludePatterns: z
|
|
34
|
+
.array(z.string())
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Glob patterns to exclude (e.g., ['*.test.ts'])"),
|
|
37
|
+
contextLines: z
|
|
38
|
+
.number()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe("Number of context lines to show around matches"),
|
|
41
|
+
}),
|
|
42
|
+
strict: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type GrepToolSchemaType = z.infer<typeof grepTool.schema>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const readTool = {
|
|
4
|
+
name: "FileRead" as const,
|
|
5
|
+
description: `Read file contents with optional pagination.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
- Provide the virtual path to the file you want to read
|
|
9
|
+
- Supports text files, images, and PDFs
|
|
10
|
+
- For large files, use offset and limit to read specific portions
|
|
11
|
+
|
|
12
|
+
The tool returns the file content in an appropriate format:
|
|
13
|
+
- Text files: Plain text content
|
|
14
|
+
- Images: Base64-encoded image data
|
|
15
|
+
- PDFs: Extracted text content
|
|
16
|
+
`,
|
|
17
|
+
schema: z.object({
|
|
18
|
+
path: z.string().describe("Virtual path to the file to read"),
|
|
19
|
+
offset: z
|
|
20
|
+
.number()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe(
|
|
23
|
+
"Line number to start reading from (1-indexed, for text files)"
|
|
24
|
+
),
|
|
25
|
+
limit: z
|
|
26
|
+
.number()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Maximum number of lines to read (for text files)"),
|
|
29
|
+
}),
|
|
30
|
+
strict: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ReadToolSchemaType = z.infer<typeof readTool.schema>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { executeChild, workflowInfo, uuid4 } from "@temporalio/workflow";
|
|
2
|
+
import type { ToolHandlerResponse } from "../../lib/tool-router";
|
|
3
|
+
import type { SubagentConfig, SubagentInput } from "../../lib/types";
|
|
4
|
+
import type { GenericTaskToolSchemaType } from "./tool";
|
|
5
|
+
|
|
6
|
+
/**
|
|
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.
|
|
18
|
+
*
|
|
19
|
+
* @param subagents - Array of subagent configurations
|
|
20
|
+
* @returns A tool handler function that can be used with the tool router
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const taskHandler = taskHandler([
|
|
24
|
+
* {
|
|
25
|
+
* name: "researcher",
|
|
26
|
+
* description: "Researches topics",
|
|
27
|
+
* workflowType: "researcherWorkflow",
|
|
28
|
+
* resultSchema: z.object({ findings: z.string() }),
|
|
29
|
+
* },
|
|
30
|
+
* ]);
|
|
31
|
+
*/
|
|
32
|
+
export function createTaskHandler(subagents: SubagentConfig[]) {
|
|
33
|
+
const { workflowId: parentWorkflowId, taskQueue: parentTaskQueue } =
|
|
34
|
+
workflowInfo();
|
|
35
|
+
|
|
36
|
+
return async (
|
|
37
|
+
args: GenericTaskToolSchemaType
|
|
38
|
+
): Promise<ToolHandlerResponse<TaskHandlerResult>> => {
|
|
39
|
+
const config = subagents.find((s) => s.name === args.subagent);
|
|
40
|
+
|
|
41
|
+
if (!config) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Unknown subagent: ${args.subagent}. Available: ${subagents.map((s) => s.name).join(", ")}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const childWorkflowId = `${parentWorkflowId}-${args.subagent}-${uuid4()}`;
|
|
48
|
+
|
|
49
|
+
// Execute the child workflow
|
|
50
|
+
const childResult = await executeChild(config.workflowType, {
|
|
51
|
+
workflowId: childWorkflowId,
|
|
52
|
+
args: [{ prompt: args.prompt } satisfies SubagentInput],
|
|
53
|
+
taskQueue: config.taskQueue ?? parentTaskQueue,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Validate result if schema provided, otherwise pass through as-is
|
|
57
|
+
const validated = config.resultSchema
|
|
58
|
+
? config.resultSchema.parse(childResult)
|
|
59
|
+
: childResult;
|
|
60
|
+
|
|
61
|
+
// Format content - stringify objects, pass strings through
|
|
62
|
+
const content =
|
|
63
|
+
typeof validated === "string"
|
|
64
|
+
? validated
|
|
65
|
+
: JSON.stringify(validated, null, 2);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
content,
|
|
69
|
+
result: {
|
|
70
|
+
result: validated,
|
|
71
|
+
childWorkflowId,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import type { SubagentConfig } from "../../lib/types";
|
|
3
|
+
|
|
4
|
+
const TASK_TOOL = "Task" as const;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Builds the tool description with available subagent information
|
|
8
|
+
*/
|
|
9
|
+
function buildTaskDescription(subagents: SubagentConfig[]): string {
|
|
10
|
+
const subagentList = subagents
|
|
11
|
+
.map((s) => `- **${s.name}**: ${s.description}`)
|
|
12
|
+
.join("\n");
|
|
13
|
+
|
|
14
|
+
return `Launch a new agent to handle complex, multi-step tasks autonomously.
|
|
15
|
+
|
|
16
|
+
The ${TASK_TOOL} tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
|
|
17
|
+
|
|
18
|
+
Available agent types:
|
|
19
|
+
|
|
20
|
+
${subagentList}
|
|
21
|
+
|
|
22
|
+
When using the ${TASK_TOOL} tool, you must specify a subagent parameter to select which agent type to use.
|
|
23
|
+
|
|
24
|
+
Usage notes:
|
|
25
|
+
|
|
26
|
+
- Always include a short description (3-5 words) summarizing what the agent will do
|
|
27
|
+
- Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
|
|
28
|
+
- When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
|
|
29
|
+
- Each invocation starts fresh - provide a detailed task description with all necessary context.
|
|
30
|
+
- Provide clear, detailed prompts so the agent can work autonomously and return exactly the information you need.
|
|
31
|
+
- The agent's outputs should generally be trusted
|
|
32
|
+
- Clearly tell the agent what type of work you expect since it is not aware of the user's intent
|
|
33
|
+
- If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a Task tool configured with the available subagents.
|
|
38
|
+
*
|
|
39
|
+
* @param subagents - Array of subagent configurations (must have at least one)
|
|
40
|
+
* @returns A tool definition with dynamic schema based on available subagents
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const taskTool = createTaskTool([
|
|
44
|
+
* {
|
|
45
|
+
* name: "researcher",
|
|
46
|
+
* description: "Researches topics and gathers information",
|
|
47
|
+
* workflowType: "researcherWorkflow",
|
|
48
|
+
* resultSchema: z.object({ findings: z.string() }),
|
|
49
|
+
* },
|
|
50
|
+
* ]);
|
|
51
|
+
*/
|
|
52
|
+
export function createTaskTool<T extends SubagentConfig[]>(
|
|
53
|
+
subagents: T
|
|
54
|
+
): {
|
|
55
|
+
name: string;
|
|
56
|
+
description: string;
|
|
57
|
+
schema: z.ZodObject<{
|
|
58
|
+
subagent: z.ZodEnum<Record<string, string>>;
|
|
59
|
+
description: z.ZodString;
|
|
60
|
+
prompt: z.ZodString;
|
|
61
|
+
}>;
|
|
62
|
+
} {
|
|
63
|
+
if (subagents.length === 0) {
|
|
64
|
+
throw new Error("createTaskTool requires at least one subagent");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const names = subagents.map((s) => s.name);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
name: TASK_TOOL,
|
|
71
|
+
description: buildTaskDescription(subagents),
|
|
72
|
+
schema: z.object({
|
|
73
|
+
subagent: z.enum(names).describe("The type of subagent to launch"),
|
|
74
|
+
description: z
|
|
75
|
+
.string()
|
|
76
|
+
.describe("A short (3-5 word) description of the task"),
|
|
77
|
+
prompt: z.string().describe("The task for the agent to perform"),
|
|
78
|
+
}),
|
|
79
|
+
} as const;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Infer the schema type for a task tool created with specific subagents
|
|
84
|
+
*/
|
|
85
|
+
export type TaskToolSchemaType<T extends SubagentConfig[]> = z.infer<
|
|
86
|
+
ReturnType<typeof createTaskTool<T>>["schema"]
|
|
87
|
+
>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Generic task tool schema type (when subagent names are not known at compile time)
|
|
91
|
+
*/
|
|
92
|
+
export type GenericTaskToolSchemaType = {
|
|
93
|
+
subagent: string;
|
|
94
|
+
description: string;
|
|
95
|
+
prompt: string;
|
|
96
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentStateManager,
|
|
3
|
+
JsonSerializable,
|
|
4
|
+
} from "../../lib/state-manager";
|
|
5
|
+
import type { ToolHandlerResponse } from "../../lib/tool-router";
|
|
6
|
+
import type { WorkflowTask } from "../../lib/types";
|
|
7
|
+
import type { TaskCreateToolSchemaType } from "./tool";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a TaskCreate handler that adds tasks to the workflow state.
|
|
11
|
+
*
|
|
12
|
+
* @param stateManager - State manager containing tasks state
|
|
13
|
+
* @param idGenerator - Function to generate unique task IDs (e.g., uuid4 from Temporal)
|
|
14
|
+
* @returns A tool handler function
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const handler = createTaskCreateHandler(stateManager, uuid4);
|
|
18
|
+
*/
|
|
19
|
+
export function createTaskCreateHandler<
|
|
20
|
+
TCustom extends JsonSerializable<TCustom>,
|
|
21
|
+
>({
|
|
22
|
+
stateManager,
|
|
23
|
+
idGenerator,
|
|
24
|
+
}: {
|
|
25
|
+
stateManager: AgentStateManager<TCustom>;
|
|
26
|
+
idGenerator: () => string;
|
|
27
|
+
}): (args: TaskCreateToolSchemaType) => ToolHandlerResponse<WorkflowTask> {
|
|
28
|
+
return (
|
|
29
|
+
args: TaskCreateToolSchemaType
|
|
30
|
+
): ToolHandlerResponse<WorkflowTask> => {
|
|
31
|
+
const task: WorkflowTask = {
|
|
32
|
+
id: idGenerator(),
|
|
33
|
+
subject: args.subject,
|
|
34
|
+
description: args.description,
|
|
35
|
+
activeForm: args.activeForm,
|
|
36
|
+
status: "pending",
|
|
37
|
+
metadata: args.metadata ?? {},
|
|
38
|
+
blockedBy: [],
|
|
39
|
+
blocks: [],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
stateManager.setTask(task);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: JSON.stringify(task, null, 2),
|
|
46
|
+
result: task,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
|
|
3
|
+
export const taskCreateTool = {
|
|
4
|
+
name: "TaskCreate" as const,
|
|
5
|
+
description: `Use this tool to create a structured task list for the control test. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
|
|
6
|
+
It also helps the user understand the progress of the task and overall progress of their requests.
|
|
7
|
+
|
|
8
|
+
## When to Use This Tool
|
|
9
|
+
|
|
10
|
+
Use this tool proactively in these scenarios:
|
|
11
|
+
|
|
12
|
+
- Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
|
|
13
|
+
- Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
|
|
14
|
+
- User explicitly requests todo list - When the user directly asks you to use the todo list
|
|
15
|
+
- User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
|
|
16
|
+
- After receiving new instructions - Immediately capture user requirements as tasks
|
|
17
|
+
- When you start working on a task - Mark it as in_progress BEFORE beginning work
|
|
18
|
+
- After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
|
|
19
|
+
|
|
20
|
+
## When NOT to Use This Tool
|
|
21
|
+
|
|
22
|
+
Skip using this tool when:
|
|
23
|
+
- There is only a single, straightforward task
|
|
24
|
+
- The task is trivial and tracking it provides no organizational benefit
|
|
25
|
+
- The task can be completed in less than 3 trivial steps
|
|
26
|
+
- The task is purely conversational or informational
|
|
27
|
+
|
|
28
|
+
NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
|
|
29
|
+
|
|
30
|
+
## Task Fields
|
|
31
|
+
|
|
32
|
+
- **subject**: A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")
|
|
33
|
+
- **description**: Detailed description of what needs to be done, including context and acceptance criteria
|
|
34
|
+
- **activeForm**: Present continuous form shown in spinner when task is in_progress (e.g., "Fixing authentication bug"). This is displayed to the user while you work on the task.
|
|
35
|
+
|
|
36
|
+
**IMPORTANT**: Always provide activeForm when creating tasks. The subject should be imperative ("Run tests") while activeForm should be present continuous ("Running tests"). All tasks are created with status \`pending\`.
|
|
37
|
+
|
|
38
|
+
## Tips
|
|
39
|
+
|
|
40
|
+
- Create tasks with clear, specific subjects that describe the outcome
|
|
41
|
+
- Include enough detail in the description for another agent to understand and complete the task
|
|
42
|
+
- After creating tasks, use TaskUpdate to set up dependencies (blocks/blockedBy) if needed
|
|
43
|
+
- Check TaskList first to avoid creating duplicate tasks`,
|
|
44
|
+
schema: z.object({
|
|
45
|
+
subject: z
|
|
46
|
+
.string()
|
|
47
|
+
.describe(
|
|
48
|
+
'A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")'
|
|
49
|
+
),
|
|
50
|
+
description: z
|
|
51
|
+
.string()
|
|
52
|
+
.describe(
|
|
53
|
+
"Detailed description of what needs to be done, including context and acceptance criteria"
|
|
54
|
+
),
|
|
55
|
+
activeForm: z
|
|
56
|
+
.string()
|
|
57
|
+
.describe(
|
|
58
|
+
'Present continuous form shown in spinner when task is in_progress (e.g., "Fixing authentication bug"). This is displayed to the user while you work on the task.'
|
|
59
|
+
),
|
|
60
|
+
metadata: z
|
|
61
|
+
.record(z.string(), z.string())
|
|
62
|
+
.describe("Arbitrary key-value pairs for tracking"),
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type TaskCreateToolSchemaType = z.infer<typeof taskCreateTool.schema>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentStateManager,
|
|
3
|
+
JsonSerializable,
|
|
4
|
+
} from "../../lib/state-manager";
|
|
5
|
+
import type { ToolHandlerResponse } from "../../lib/tool-router";
|
|
6
|
+
import type { WorkflowTask } from "../../lib/types";
|
|
7
|
+
import type { TaskGetToolSchemaType } from "./tool";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a TaskGet handler that retrieves a task by ID.
|
|
11
|
+
*
|
|
12
|
+
* @param stateManager - State manager containing tasks state
|
|
13
|
+
* @returns A tool handler function
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const handler = createTaskGetHandler(stateManager);
|
|
17
|
+
*/
|
|
18
|
+
export function createTaskGetHandler<TCustom extends JsonSerializable<TCustom>>(
|
|
19
|
+
stateManager: AgentStateManager<TCustom>
|
|
20
|
+
): (args: TaskGetToolSchemaType) => ToolHandlerResponse<WorkflowTask | null> {
|
|
21
|
+
return (
|
|
22
|
+
args: TaskGetToolSchemaType
|
|
23
|
+
): ToolHandlerResponse<WorkflowTask | null> => {
|
|
24
|
+
const task = stateManager.getTask(args.taskId) ?? null;
|
|
25
|
+
|
|
26
|
+
if (!task) {
|
|
27
|
+
return {
|
|
28
|
+
content: JSON.stringify({ error: `Task not found: ${args.taskId}` }),
|
|
29
|
+
result: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
content: JSON.stringify(task, null, 2),
|
|
35
|
+
result: task,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}
|