illuma-agents 1.0.66 → 1.0.67
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/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/main.cjs +19 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/prompts/collab.cjs +11 -0
- package/dist/cjs/prompts/collab.cjs.map +1 -0
- package/dist/cjs/prompts/taskmanager.cjs +66 -0
- package/dist/cjs/prompts/taskmanager.cjs.map +1 -0
- package/dist/cjs/prompts/taskplanning.cjs +99 -0
- package/dist/cjs/prompts/taskplanning.cjs.map +1 -0
- package/dist/cjs/tools/TaskProgress.cjs +172 -0
- package/dist/cjs/tools/TaskProgress.cjs.map +1 -0
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/prompts/collab.mjs +9 -0
- package/dist/esm/prompts/collab.mjs.map +1 -0
- package/dist/esm/prompts/taskmanager.mjs +60 -0
- package/dist/esm/prompts/taskmanager.mjs.map +1 -0
- package/dist/esm/prompts/taskplanning.mjs +94 -0
- package/dist/esm/prompts/taskplanning.mjs.map +1 -0
- package/dist/esm/tools/TaskProgress.mjs +163 -0
- package/dist/esm/tools/TaskProgress.mjs.map +1 -0
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/prompts/index.d.ts +1 -0
- package/dist/types/prompts/taskplanning.d.ts +54 -0
- package/dist/types/tools/TaskProgress.d.ts +142 -0
- package/package.json +1 -1
- package/src/common/enum.ts +2 -0
- package/src/index.ts +4 -0
- package/src/prompts/index.ts +2 -1
- package/src/prompts/taskplanning.ts +96 -0
- package/src/specs/task-progress.test.ts +330 -0
- package/src/tools/TaskProgress.ts +247 -0
package/src/prompts/index.ts
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/prompts/taskplanning.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* System prompt fragment for task planning capabilities.
|
|
5
|
+
*
|
|
6
|
+
* Include this in the agent's system prompt when:
|
|
7
|
+
* - Agent is ephemeral (not a persistent workflow)
|
|
8
|
+
* - Agent handles complex, multi-step research or coding tasks
|
|
9
|
+
* - You want visible progress tracking for the user
|
|
10
|
+
*
|
|
11
|
+
* Do NOT include for:
|
|
12
|
+
* - Simple tool-calling agents
|
|
13
|
+
* - Workflow/builder agents with predefined steps
|
|
14
|
+
* - Agents with fixed execution patterns
|
|
15
|
+
*/
|
|
16
|
+
export const taskPlanningPrompt = `## Task Planning & Progress
|
|
17
|
+
|
|
18
|
+
You have access to a \`manage_todo_list\` tool for tracking complex tasks.
|
|
19
|
+
|
|
20
|
+
### When to Use Task Planning
|
|
21
|
+
- **USE** for complex work requiring 3+ steps (research, multi-file changes, analysis)
|
|
22
|
+
- **SKIP** for simple requests (single file edit, quick lookup, conversational responses)
|
|
23
|
+
|
|
24
|
+
### Task Workflow
|
|
25
|
+
1. **Plan First**: Before starting complex work, create a todo list with 3-6 clear, actionable items
|
|
26
|
+
2. **One at a Time**: Mark ONE task as "in-progress" before starting work on it
|
|
27
|
+
3. **Multiple Tool Calls OK**: A single task may require multiple tool calls (searches, reads, writes)
|
|
28
|
+
4. **Complete Immediately**: Mark task "completed" right after finishing, don't batch updates
|
|
29
|
+
5. **Iterate**: Move to next task, repeat until done
|
|
30
|
+
|
|
31
|
+
### Task Best Practices
|
|
32
|
+
- Keep titles short and action-oriented (3-7 words): "Research API options", "Implement auth flow"
|
|
33
|
+
- Group related work into single tasks rather than micro-tasks
|
|
34
|
+
- Update status promptly so user sees real-time progress
|
|
35
|
+
- For simple 1-2 step work, just do it directly without creating todos
|
|
36
|
+
|
|
37
|
+
### Example Task Breakdown
|
|
38
|
+
For "Add user authentication to the app":
|
|
39
|
+
1. Review existing auth code and dependencies
|
|
40
|
+
2. Implement login endpoint
|
|
41
|
+
3. Add session management
|
|
42
|
+
4. Create protected route middleware
|
|
43
|
+
5. Test authentication flow
|
|
44
|
+
|
|
45
|
+
Each task above might involve multiple file reads, code writes, and searches - that's expected.
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Short version of task planning prompt for constrained context
|
|
50
|
+
*/
|
|
51
|
+
export const taskPlanningPromptShort = `## Task Tracking
|
|
52
|
+
Use \`manage_todo_list\` for complex multi-step work (3+ steps).
|
|
53
|
+
- Create 3-6 actionable todos before starting
|
|
54
|
+
- Mark one "in-progress" at a time
|
|
55
|
+
- Mark "completed" immediately after finishing
|
|
56
|
+
- Skip for simple 1-2 step tasks
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Function description for structured output agents
|
|
61
|
+
*/
|
|
62
|
+
export const manageTodoListFunctionDescription =
|
|
63
|
+
'Manage a todo list for tracking progress on complex multi-step tasks. Create todos before starting work, mark in-progress while working, mark completed when done.';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Function parameters schema (for agents using function calling format)
|
|
67
|
+
*/
|
|
68
|
+
export const manageTodoListFunctionParameters = {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
todoList: {
|
|
72
|
+
type: 'array',
|
|
73
|
+
description: 'Complete array of all todo items (existing and new)',
|
|
74
|
+
items: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
id: {
|
|
78
|
+
type: 'number',
|
|
79
|
+
description: 'Sequential ID starting from 1',
|
|
80
|
+
},
|
|
81
|
+
title: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Short action-oriented title (3-7 words)',
|
|
84
|
+
},
|
|
85
|
+
status: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
enum: ['not-started', 'in-progress', 'completed'],
|
|
88
|
+
description: 'Current task status',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
required: ['id', 'title', 'status'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
required: ['todoList'],
|
|
96
|
+
};
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// src/specs/task-progress.test.ts
|
|
2
|
+
import { GraphEvents } from '@/common';
|
|
3
|
+
import {
|
|
4
|
+
createTaskProgressTool,
|
|
5
|
+
TaskProgressToolName,
|
|
6
|
+
TaskProgressToolDescription,
|
|
7
|
+
TaskProgressToolSchema,
|
|
8
|
+
TaskProgressToolDefinition,
|
|
9
|
+
type TaskItem,
|
|
10
|
+
type TaskStatus,
|
|
11
|
+
type TaskProgressPayload,
|
|
12
|
+
} from '@/tools/TaskProgress';
|
|
13
|
+
|
|
14
|
+
// Mock the safeDispatchCustomEvent
|
|
15
|
+
jest.mock('@/utils/events', () => ({
|
|
16
|
+
safeDispatchCustomEvent: jest.fn().mockResolvedValue(undefined),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
20
|
+
const mockDispatch = safeDispatchCustomEvent as jest.MockedFunction<
|
|
21
|
+
typeof safeDispatchCustomEvent
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
describe('TaskProgress Tool', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
jest.clearAllMocks();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('Tool Definition', () => {
|
|
30
|
+
it('should have correct tool name', () => {
|
|
31
|
+
expect(TaskProgressToolName).toBe('manage_todo_list');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should have description with usage guidelines', () => {
|
|
35
|
+
expect(TaskProgressToolDescription).toContain('todo list');
|
|
36
|
+
expect(TaskProgressToolDescription).toContain('not-started');
|
|
37
|
+
expect(TaskProgressToolDescription).toContain('in-progress');
|
|
38
|
+
expect(TaskProgressToolDescription).toContain('completed');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should have valid JSON schema', () => {
|
|
42
|
+
expect(TaskProgressToolSchema.type).toBe('object');
|
|
43
|
+
expect(TaskProgressToolSchema.required).toContain('todoList');
|
|
44
|
+
expect(TaskProgressToolSchema.properties.todoList.type).toBe('array');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should export complete tool definition', () => {
|
|
48
|
+
expect(TaskProgressToolDefinition.name).toBe(TaskProgressToolName);
|
|
49
|
+
expect(TaskProgressToolDefinition.description).toBe(
|
|
50
|
+
TaskProgressToolDescription
|
|
51
|
+
);
|
|
52
|
+
expect(TaskProgressToolDefinition.schema).toEqual(TaskProgressToolSchema);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('createTaskProgressTool', () => {
|
|
57
|
+
it('should create a tool with correct name', () => {
|
|
58
|
+
const tool = createTaskProgressTool();
|
|
59
|
+
expect(tool.name).toBe('manage_todo_list');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should create a tool with description', () => {
|
|
63
|
+
const tool = createTaskProgressTool();
|
|
64
|
+
expect(tool.description).toBeDefined();
|
|
65
|
+
expect(tool.description.length).toBeGreaterThan(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should accept onTaskUpdate callback parameter', () => {
|
|
69
|
+
const mockCallback = jest.fn();
|
|
70
|
+
const tool = createTaskProgressTool({ onTaskUpdate: mockCallback });
|
|
71
|
+
expect(tool).toBeDefined();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('Tool Invocation', () => {
|
|
76
|
+
it('should accept valid todo list and return success message', async () => {
|
|
77
|
+
const tool = createTaskProgressTool();
|
|
78
|
+
|
|
79
|
+
const validTodoList: TaskItem[] = [
|
|
80
|
+
{ id: 1, title: 'Research API', status: 'not-started' },
|
|
81
|
+
{ id: 2, title: 'Implement feature', status: 'not-started' },
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const result = await tool.invoke({ todoList: validTodoList });
|
|
85
|
+
|
|
86
|
+
expect(result).toContain('Todo list updated successfully');
|
|
87
|
+
expect(result).toContain('0/2 completed');
|
|
88
|
+
expect(result).toContain('2 remaining');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should dispatch SSE event with correct event type', async () => {
|
|
92
|
+
const tool = createTaskProgressTool();
|
|
93
|
+
|
|
94
|
+
const todoList: TaskItem[] = [
|
|
95
|
+
{ id: 1, title: 'Task 1', status: 'in-progress' },
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
await tool.invoke({ todoList });
|
|
99
|
+
|
|
100
|
+
expect(mockDispatch).toHaveBeenCalledWith(
|
|
101
|
+
GraphEvents.ON_TASK_PROGRESS,
|
|
102
|
+
expect.objectContaining({
|
|
103
|
+
tasks: todoList,
|
|
104
|
+
timestamp: expect.any(String),
|
|
105
|
+
isComplete: false,
|
|
106
|
+
}),
|
|
107
|
+
expect.anything() // config object is passed
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should include timestamp in dispatched payload', async () => {
|
|
112
|
+
const tool = createTaskProgressTool();
|
|
113
|
+
|
|
114
|
+
const todoList: TaskItem[] = [
|
|
115
|
+
{ id: 1, title: 'Task', status: 'not-started' },
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
await tool.invoke({ todoList });
|
|
119
|
+
|
|
120
|
+
const dispatchedPayload = mockDispatch.mock.calls[0][1] as TaskProgressPayload;
|
|
121
|
+
expect(dispatchedPayload.timestamp).toBeDefined();
|
|
122
|
+
expect(new Date(dispatchedPayload.timestamp).getTime()).toBeGreaterThan(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should call onTaskUpdate callback when provided', async () => {
|
|
126
|
+
const mockCallback = jest.fn();
|
|
127
|
+
const tool = createTaskProgressTool({ onTaskUpdate: mockCallback });
|
|
128
|
+
|
|
129
|
+
const todoList: TaskItem[] = [
|
|
130
|
+
{ id: 1, title: 'Task', status: 'completed' },
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
await tool.invoke({ todoList });
|
|
134
|
+
|
|
135
|
+
expect(mockCallback).toHaveBeenCalledWith(todoList);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should handle async onTaskUpdate callback', async () => {
|
|
139
|
+
const asyncCallback = jest.fn().mockResolvedValue(undefined);
|
|
140
|
+
const tool = createTaskProgressTool({ onTaskUpdate: asyncCallback });
|
|
141
|
+
|
|
142
|
+
const todoList: TaskItem[] = [
|
|
143
|
+
{ id: 1, title: 'Task', status: 'in-progress' },
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
await tool.invoke({ todoList });
|
|
147
|
+
|
|
148
|
+
expect(asyncCallback).toHaveBeenCalledWith(todoList);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should not fail if onTaskUpdate throws', async () => {
|
|
152
|
+
const failingCallback = jest.fn().mockRejectedValue(new Error('Callback error'));
|
|
153
|
+
const tool = createTaskProgressTool({ onTaskUpdate: failingCallback });
|
|
154
|
+
|
|
155
|
+
const todoList: TaskItem[] = [
|
|
156
|
+
{ id: 1, title: 'Task', status: 'in-progress' },
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
// Should not throw, just warn
|
|
160
|
+
const result = await tool.invoke({ todoList });
|
|
161
|
+
expect(result).toContain('Todo list updated successfully');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('Status Tracking', () => {
|
|
166
|
+
it('should correctly count completed tasks', async () => {
|
|
167
|
+
const tool = createTaskProgressTool();
|
|
168
|
+
|
|
169
|
+
const todoList: TaskItem[] = [
|
|
170
|
+
{ id: 1, title: 'Done 1', status: 'completed' },
|
|
171
|
+
{ id: 2, title: 'Done 2', status: 'completed' },
|
|
172
|
+
{ id: 3, title: 'Not done', status: 'not-started' },
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const result = await tool.invoke({ todoList });
|
|
176
|
+
|
|
177
|
+
expect(result).toContain('2/3 completed');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should correctly count in-progress tasks', async () => {
|
|
181
|
+
const tool = createTaskProgressTool();
|
|
182
|
+
|
|
183
|
+
const todoList: TaskItem[] = [
|
|
184
|
+
{ id: 1, title: 'Working', status: 'in-progress' },
|
|
185
|
+
{ id: 2, title: 'Not started', status: 'not-started' },
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
const result = await tool.invoke({ todoList });
|
|
189
|
+
|
|
190
|
+
expect(result).toContain('1 in progress');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should correctly count not-started tasks', async () => {
|
|
194
|
+
const tool = createTaskProgressTool();
|
|
195
|
+
|
|
196
|
+
const todoList: TaskItem[] = [
|
|
197
|
+
{ id: 1, title: 'Task 1', status: 'not-started' },
|
|
198
|
+
{ id: 2, title: 'Task 2', status: 'not-started' },
|
|
199
|
+
{ id: 3, title: 'Task 3', status: 'completed' },
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
const result = await tool.invoke({ todoList });
|
|
203
|
+
|
|
204
|
+
expect(result).toContain('2 remaining');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should warn about multiple in-progress tasks', async () => {
|
|
208
|
+
const tool = createTaskProgressTool();
|
|
209
|
+
|
|
210
|
+
const todoList: TaskItem[] = [
|
|
211
|
+
{ id: 1, title: 'Task 1', status: 'in-progress' },
|
|
212
|
+
{ id: 2, title: 'Task 2', status: 'in-progress' },
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const result = await tool.invoke({ todoList });
|
|
216
|
+
|
|
217
|
+
expect(result).toContain('Warning');
|
|
218
|
+
expect(result).toContain('2 tasks marked as in-progress');
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should support failed status', async () => {
|
|
222
|
+
const tool = createTaskProgressTool();
|
|
223
|
+
|
|
224
|
+
const todoList: TaskItem[] = [
|
|
225
|
+
{ id: 1, title: 'Failed task', status: 'failed' },
|
|
226
|
+
{ id: 2, title: 'Completed', status: 'completed' },
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const result = await tool.invoke({ todoList });
|
|
230
|
+
|
|
231
|
+
expect(result).toContain('Todo list updated successfully');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('Validation', () => {
|
|
236
|
+
it('should throw on non-array todoList (schema validation)', async () => {
|
|
237
|
+
const tool = createTaskProgressTool();
|
|
238
|
+
|
|
239
|
+
// LangChain schema validation catches this before our code runs
|
|
240
|
+
await expect(
|
|
241
|
+
tool.invoke({ todoList: 'not an array' })
|
|
242
|
+
).rejects.toThrow();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should throw on tasks with non-numeric id (schema validation)', async () => {
|
|
246
|
+
const tool = createTaskProgressTool();
|
|
247
|
+
|
|
248
|
+
const invalidTodoList = [
|
|
249
|
+
{ id: 'abc', title: 'Task', status: 'not-started' },
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
// LangChain schema validation catches this before our code runs
|
|
253
|
+
await expect(
|
|
254
|
+
tool.invoke({ todoList: invalidTodoList })
|
|
255
|
+
).rejects.toThrow();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should reject tasks with empty title', async () => {
|
|
259
|
+
const tool = createTaskProgressTool();
|
|
260
|
+
|
|
261
|
+
const invalidTodoList = [{ id: 1, title: '', status: 'not-started' }];
|
|
262
|
+
|
|
263
|
+
const result = await tool.invoke({ todoList: invalidTodoList });
|
|
264
|
+
|
|
265
|
+
expect(result).toContain('Error');
|
|
266
|
+
expect(result).toContain('title');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should throw on tasks with invalid status (schema validation)', async () => {
|
|
270
|
+
const tool = createTaskProgressTool();
|
|
271
|
+
|
|
272
|
+
const invalidTodoList = [
|
|
273
|
+
{ id: 1, title: 'Task', status: 'invalid-status' },
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
// LangChain schema validation catches this before our code runs
|
|
277
|
+
await expect(
|
|
278
|
+
tool.invoke({ todoList: invalidTodoList })
|
|
279
|
+
).rejects.toThrow();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should accept empty todo list', async () => {
|
|
283
|
+
const tool = createTaskProgressTool();
|
|
284
|
+
|
|
285
|
+
const result = await tool.invoke({ todoList: [] });
|
|
286
|
+
|
|
287
|
+
expect(result).toContain('Todo list updated successfully');
|
|
288
|
+
expect(result).toContain('0/0 completed');
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('Type Exports', () => {
|
|
293
|
+
it('should export TaskStatus type with correct values', () => {
|
|
294
|
+
const validStatuses: TaskStatus[] = [
|
|
295
|
+
'not-started',
|
|
296
|
+
'in-progress',
|
|
297
|
+
'completed',
|
|
298
|
+
'failed',
|
|
299
|
+
];
|
|
300
|
+
|
|
301
|
+
validStatuses.forEach((status) => {
|
|
302
|
+
expect(['not-started', 'in-progress', 'completed', 'failed']).toContain(
|
|
303
|
+
status
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should export TaskItem type with required fields', () => {
|
|
309
|
+
const validTask: TaskItem = {
|
|
310
|
+
id: 1,
|
|
311
|
+
title: 'Test task',
|
|
312
|
+
status: 'not-started',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
expect(validTask.id).toBe(1);
|
|
316
|
+
expect(validTask.title).toBe('Test task');
|
|
317
|
+
expect(validTask.status).toBe('not-started');
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should export TaskProgressPayload type', () => {
|
|
321
|
+
const payload: TaskProgressPayload = {
|
|
322
|
+
tasks: [{ id: 1, title: 'Test', status: 'completed' }],
|
|
323
|
+
timestamp: new Date().toISOString(),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
expect(payload.tasks).toBeDefined();
|
|
327
|
+
expect(payload.timestamp).toBeDefined();
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/tools/TaskProgress.ts
|
|
2
|
+
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
3
|
+
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
4
|
+
import { GraphEvents } from '@/common';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Task status enum matching the UI component expectations
|
|
8
|
+
*/
|
|
9
|
+
export type TaskStatus = 'not-started' | 'in-progress' | 'completed' | 'failed';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Individual task item in the todo list
|
|
13
|
+
*/
|
|
14
|
+
export interface TaskItem {
|
|
15
|
+
/** Unique identifier for the task (sequential number starting from 1) */
|
|
16
|
+
id: number;
|
|
17
|
+
/** Short, action-oriented title (3-7 words) */
|
|
18
|
+
title: string;
|
|
19
|
+
/** Current status of the task */
|
|
20
|
+
status: TaskStatus;
|
|
21
|
+
/** Optional message for additional context */
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Payload sent via SSE event for task progress updates
|
|
27
|
+
* This matches the format expected by the Ranger UI
|
|
28
|
+
*/
|
|
29
|
+
export interface TaskProgressPayload {
|
|
30
|
+
/** Complete list of all tasks */
|
|
31
|
+
tasks: TaskItem[];
|
|
32
|
+
/** Timestamp of the update */
|
|
33
|
+
timestamp: string;
|
|
34
|
+
/** Optional title for the task group */
|
|
35
|
+
title?: string;
|
|
36
|
+
/** Whether all tasks are complete */
|
|
37
|
+
isComplete?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Tool name constant - matches what AI calls
|
|
42
|
+
*/
|
|
43
|
+
export const TaskProgressToolName = 'manage_todo_list';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tool description for the AI
|
|
47
|
+
*/
|
|
48
|
+
export const TaskProgressToolDescription = `Manage a structured todo list to track progress and plan tasks throughout your session.
|
|
49
|
+
|
|
50
|
+
**When to use this tool:**
|
|
51
|
+
- Complex multi-step work requiring planning and tracking (3+ steps)
|
|
52
|
+
- When breaking down larger tasks into smaller actionable steps
|
|
53
|
+
- Before starting work on any todo (mark as in-progress)
|
|
54
|
+
- Immediately after completing each todo (mark as completed)
|
|
55
|
+
- To give users visibility into your progress and planning
|
|
56
|
+
|
|
57
|
+
**When NOT to use:**
|
|
58
|
+
- Single, trivial tasks completed in one step
|
|
59
|
+
- Purely conversational/informational requests
|
|
60
|
+
- Simple file reads or searches
|
|
61
|
+
|
|
62
|
+
**Critical workflow:**
|
|
63
|
+
1. Plan tasks by creating todo list with specific, actionable items
|
|
64
|
+
2. Mark ONE todo as in-progress before starting work
|
|
65
|
+
3. Complete the work (may involve multiple tool calls)
|
|
66
|
+
4. Mark that todo as completed IMMEDIATELY
|
|
67
|
+
5. Move to next todo and repeat
|
|
68
|
+
|
|
69
|
+
**Todo states:**
|
|
70
|
+
- not-started: Todo not yet begun
|
|
71
|
+
- in-progress: Currently working (limit ONE at a time)
|
|
72
|
+
- completed: Finished successfully
|
|
73
|
+
- failed: Task could not be completed
|
|
74
|
+
|
|
75
|
+
**Important:** Mark todos completed as soon as they are done. Do not batch completions.`;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* JSON Schema for the tool input
|
|
79
|
+
*/
|
|
80
|
+
export const TaskProgressToolSchema = {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
todoList: {
|
|
84
|
+
type: 'array',
|
|
85
|
+
description:
|
|
86
|
+
'Complete array of all todo items. Must include ALL items - both existing and new.',
|
|
87
|
+
items: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
id: {
|
|
91
|
+
type: 'number',
|
|
92
|
+
description:
|
|
93
|
+
'Unique identifier for the todo. Use sequential numbers starting from 1.',
|
|
94
|
+
},
|
|
95
|
+
title: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
description:
|
|
98
|
+
'Concise action-oriented todo label (3-7 words). Displayed in UI.',
|
|
99
|
+
},
|
|
100
|
+
status: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
enum: ['not-started', 'in-progress', 'completed', 'failed'],
|
|
103
|
+
description:
|
|
104
|
+
'not-started: Not begun | in-progress: Currently working (max 1) | completed: Fully finished | failed: Could not complete',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
required: ['id', 'title', 'status'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ['todoList'],
|
|
112
|
+
} as const;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Tool definition for registration/export
|
|
116
|
+
*/
|
|
117
|
+
export const TaskProgressToolDefinition = {
|
|
118
|
+
name: TaskProgressToolName,
|
|
119
|
+
description: TaskProgressToolDescription,
|
|
120
|
+
schema: TaskProgressToolSchema,
|
|
121
|
+
} as const;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Parameters for creating the TaskProgress tool
|
|
125
|
+
*/
|
|
126
|
+
export interface TaskProgressToolParams {
|
|
127
|
+
/**
|
|
128
|
+
* Optional callback to handle task updates externally
|
|
129
|
+
* Called with the task list whenever it's updated
|
|
130
|
+
*/
|
|
131
|
+
onTaskUpdate?: (tasks: TaskItem[]) => void | Promise<void>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates a TaskProgress tool that allows the AI to manage a todo list
|
|
136
|
+
* and emits events for the UI to display progress.
|
|
137
|
+
*
|
|
138
|
+
* This tool is designed for ephemeral agents doing complex, multi-step work.
|
|
139
|
+
* It should NOT be included for:
|
|
140
|
+
* - Persistent workflow agents
|
|
141
|
+
* - Simple tool-calling agents
|
|
142
|
+
* - No-code/low-code builder workflows (unless explicitly enabled)
|
|
143
|
+
*
|
|
144
|
+
* @param params - Optional parameters including external callback
|
|
145
|
+
* @returns DynamicStructuredTool that can be added to agent tools
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```typescript
|
|
149
|
+
* // Add to agent tools for ephemeral research/planning agents
|
|
150
|
+
* const tools = [
|
|
151
|
+
* createTaskProgressTool(),
|
|
152
|
+
* // ... other tools
|
|
153
|
+
* ];
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export function createTaskProgressTool(
|
|
157
|
+
params: TaskProgressToolParams = {}
|
|
158
|
+
): DynamicStructuredTool {
|
|
159
|
+
const { onTaskUpdate } = params;
|
|
160
|
+
|
|
161
|
+
return tool(
|
|
162
|
+
async (rawInput, config) => {
|
|
163
|
+
const { todoList } = rawInput as { todoList: TaskItem[] };
|
|
164
|
+
|
|
165
|
+
// Validate input
|
|
166
|
+
if (!Array.isArray(todoList)) {
|
|
167
|
+
return 'Error: todoList must be an array of task items.';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Validate each task
|
|
171
|
+
for (const task of todoList) {
|
|
172
|
+
if (typeof task.id !== 'number') {
|
|
173
|
+
return `Error: Task id must be a number. Got: ${typeof task.id}`;
|
|
174
|
+
}
|
|
175
|
+
if (typeof task.title !== 'string' || task.title.trim() === '') {
|
|
176
|
+
return `Error: Task title must be a non-empty string for task ${task.id}`;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
!['not-started', 'in-progress', 'completed', 'failed'].includes(
|
|
180
|
+
task.status
|
|
181
|
+
)
|
|
182
|
+
) {
|
|
183
|
+
return `Error: Invalid status "${task.status}" for task ${task.id}. Must be one of: not-started, in-progress, completed, failed`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Check for multiple in-progress tasks (warning, not error)
|
|
188
|
+
const inProgressCount = todoList.filter(
|
|
189
|
+
(t) => t.status === 'in-progress'
|
|
190
|
+
).length;
|
|
191
|
+
const warning =
|
|
192
|
+
inProgressCount > 1
|
|
193
|
+
? `\nWarning: ${inProgressCount} tasks marked as in-progress. Best practice is to work on one task at a time.`
|
|
194
|
+
: '';
|
|
195
|
+
|
|
196
|
+
// Check if all tasks are complete
|
|
197
|
+
const allComplete =
|
|
198
|
+
todoList.length > 0 &&
|
|
199
|
+
todoList.every(
|
|
200
|
+
(t) => t.status === 'completed' || t.status === 'failed'
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// Create the payload for SSE event
|
|
204
|
+
const payload: TaskProgressPayload = {
|
|
205
|
+
tasks: todoList,
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
isComplete: allComplete,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Dispatch SSE event for UI to consume
|
|
211
|
+
await safeDispatchCustomEvent(
|
|
212
|
+
GraphEvents.ON_TASK_PROGRESS,
|
|
213
|
+
payload,
|
|
214
|
+
config
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Call external callback if provided
|
|
218
|
+
if (onTaskUpdate) {
|
|
219
|
+
try {
|
|
220
|
+
await onTaskUpdate(todoList);
|
|
221
|
+
} catch (e) {
|
|
222
|
+
// Don't fail the tool call if callback fails
|
|
223
|
+
// eslint-disable-next-line no-console
|
|
224
|
+
console.warn('TaskProgress callback error:', e);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Build response message
|
|
229
|
+
const completed = todoList.filter((t) => t.status === 'completed').length;
|
|
230
|
+
const inProgress = todoList.filter(
|
|
231
|
+
(t) => t.status === 'in-progress'
|
|
232
|
+
).length;
|
|
233
|
+
const notStarted = todoList.filter(
|
|
234
|
+
(t) => t.status === 'not-started'
|
|
235
|
+
).length;
|
|
236
|
+
|
|
237
|
+
return `Todo list updated successfully. Status: ${completed}/${todoList.length} completed, ${inProgress} in progress, ${notStarted} remaining.${warning}`;
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: TaskProgressToolName,
|
|
241
|
+
description: TaskProgressToolDescription,
|
|
242
|
+
schema: TaskProgressToolSchema,
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export default createTaskProgressTool;
|