@agentscope-ai/agentscope 0.0.2
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/agent/index.d.mts +234 -0
- package/dist/agent/index.d.ts +234 -0
- package/dist/agent/index.js +1412 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/index.mjs +1375 -0
- package/dist/agent/index.mjs.map +1 -0
- package/dist/base-BOx3UzOl.d.mts +41 -0
- package/dist/base-BoIps2RL.d.ts +41 -0
- package/dist/base-C7jwyH4Z.d.mts +52 -0
- package/dist/base-Cwi4bjze.d.ts +127 -0
- package/dist/base-DYlBMCy_.d.mts +127 -0
- package/dist/base-NX-knWOv.d.ts +52 -0
- package/dist/block-VsnHrllL.d.mts +48 -0
- package/dist/block-VsnHrllL.d.ts +48 -0
- package/dist/event/index.d.mts +181 -0
- package/dist/event/index.d.ts +181 -0
- package/dist/event/index.js +58 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/index.mjs +33 -0
- package/dist/event/index.mjs.map +1 -0
- package/dist/formatter/index.d.mts +187 -0
- package/dist/formatter/index.d.ts +187 -0
- package/dist/formatter/index.js +647 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/formatter/index.mjs +616 -0
- package/dist/formatter/index.mjs.map +1 -0
- package/dist/index-BTJDlKvQ.d.mts +195 -0
- package/dist/index-BcatlwXQ.d.ts +195 -0
- package/dist/index-CAxQAkiP.d.mts +21 -0
- package/dist/index-CAxQAkiP.d.ts +21 -0
- package/dist/mcp/index.d.mts +9 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.js +432 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +408 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/message/index.d.mts +10 -0
- package/dist/message/index.d.ts +10 -0
- package/dist/message/index.js +67 -0
- package/dist/message/index.js.map +1 -0
- package/dist/message/index.mjs +37 -0
- package/dist/message/index.mjs.map +1 -0
- package/dist/message-CkN21KaY.d.mts +99 -0
- package/dist/message-CzLeTlua.d.ts +99 -0
- package/dist/model/index.d.mts +377 -0
- package/dist/model/index.d.ts +377 -0
- package/dist/model/index.js +1880 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/index.mjs +1849 -0
- package/dist/model/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +68 -0
- package/dist/storage/index.d.ts +68 -0
- package/dist/storage/index.js +250 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +212 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/tool/index.d.mts +311 -0
- package/dist/tool/index.d.ts +311 -0
- package/dist/tool/index.js +1494 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/index.mjs +1447 -0
- package/dist/tool/index.mjs.map +1 -0
- package/dist/toolkit-CEpulFi0.d.ts +99 -0
- package/dist/toolkit-CGEZSZPa.d.mts +99 -0
- package/jest.config.js +11 -0
- package/package.json +92 -0
- package/src/_utils/common.ts +104 -0
- package/src/_utils/index.ts +1 -0
- package/src/agent/agent-base.ts +0 -0
- package/src/agent/agent.test.ts +1028 -0
- package/src/agent/agent.ts +1032 -0
- package/src/agent/index.ts +2 -0
- package/src/agent/interfaces.ts +23 -0
- package/src/agent/test-compression.ts +72 -0
- package/src/event/index.ts +250 -0
- package/src/formatter/base.ts +133 -0
- package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
- package/src/formatter/dashscope-chat-formatter.ts +163 -0
- package/src/formatter/deepseek-chat-formatter.ts +130 -0
- package/src/formatter/index.ts +5 -0
- package/src/formatter/ollama-chat-formatter.ts +67 -0
- package/src/formatter/openai-chat-formatter.test.ts +263 -0
- package/src/formatter/openai-chat-formatter.ts +301 -0
- package/src/formatter/openai.md +767 -0
- package/src/mcp/base.ts +114 -0
- package/src/mcp/http.test.ts +303 -0
- package/src/mcp/http.ts +224 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/stdio.test.ts +91 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/message/block.ts +60 -0
- package/src/message/enums.ts +4 -0
- package/src/message/index.ts +12 -0
- package/src/message/message.test.ts +80 -0
- package/src/message/message.ts +131 -0
- package/src/model/base.ts +226 -0
- package/src/model/dashscope-model.test.ts +335 -0
- package/src/model/dashscope-model.ts +441 -0
- package/src/model/deepseek-model.test.ts +279 -0
- package/src/model/deepseek-model.ts +401 -0
- package/src/model/index.ts +7 -0
- package/src/model/ollama-model.test.ts +307 -0
- package/src/model/ollama-model.ts +356 -0
- package/src/model/openai-model.ts +327 -0
- package/src/model/response.ts +22 -0
- package/src/model/usage.ts +12 -0
- package/src/storage/base.ts +52 -0
- package/src/storage/file-system.test.ts +587 -0
- package/src/storage/file-system.ts +269 -0
- package/src/storage/index.ts +2 -0
- package/src/tool/base.ts +23 -0
- package/src/tool/bash.test.ts +174 -0
- package/src/tool/bash.ts +152 -0
- package/src/tool/edit.test.ts +83 -0
- package/src/tool/edit.ts +95 -0
- package/src/tool/glob.test.ts +63 -0
- package/src/tool/glob.ts +166 -0
- package/src/tool/grep.test.ts +74 -0
- package/src/tool/grep.ts +256 -0
- package/src/tool/index.ts +10 -0
- package/src/tool/read.test.ts +77 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/response.ts +82 -0
- package/src/tool/task.test.ts +299 -0
- package/src/tool/task.ts +399 -0
- package/src/tool/toolkit.test.ts +636 -0
- package/src/tool/toolkit.ts +601 -0
- package/src/tool/write.test.ts +52 -0
- package/src/tool/write.ts +57 -0
- package/src/type/index.ts +52 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.esm.json +10 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +52 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { Tool } from './base';
|
|
2
|
+
import { ToolResponse } from './response';
|
|
3
|
+
import { TaskCreate, TaskUpdate, TaskGet, TaskList, _resetTaskStore } from './task';
|
|
4
|
+
|
|
5
|
+
describe('Task Tools', () => {
|
|
6
|
+
let taskCreate: Tool;
|
|
7
|
+
let taskUpdate: Tool;
|
|
8
|
+
let taskGet: Tool;
|
|
9
|
+
let taskList: Tool;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
_resetTaskStore();
|
|
13
|
+
taskCreate = TaskCreate();
|
|
14
|
+
taskUpdate = TaskUpdate();
|
|
15
|
+
taskGet = TaskGet();
|
|
16
|
+
taskList = TaskList();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const getTextFromResponse = (response: ToolResponse): string => {
|
|
20
|
+
const textBlock = response.content.find(block => block.type === 'text');
|
|
21
|
+
return textBlock && 'text' in textBlock ? textBlock.text : '';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe('TaskCreate', () => {
|
|
25
|
+
it('creates a task with pending status', () => {
|
|
26
|
+
const response = taskCreate.call!({
|
|
27
|
+
subject: 'Fix bug',
|
|
28
|
+
description: 'Fix the authentication bug',
|
|
29
|
+
}) as ToolResponse;
|
|
30
|
+
const result = getTextFromResponse(response);
|
|
31
|
+
expect(result).toContain('Task #1 created successfully');
|
|
32
|
+
expect(result).toContain('Fix bug');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('increments task IDs', () => {
|
|
36
|
+
taskCreate.call!({ subject: 'Task 1', description: 'First task' });
|
|
37
|
+
const response = taskCreate.call!({
|
|
38
|
+
subject: 'Task 2',
|
|
39
|
+
description: 'Second task',
|
|
40
|
+
}) as ToolResponse;
|
|
41
|
+
const result = getTextFromResponse(response);
|
|
42
|
+
expect(result).toContain('Task #2 created successfully');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('supports optional activeForm and metadata', () => {
|
|
46
|
+
const response = taskCreate.call!({
|
|
47
|
+
subject: 'Run tests',
|
|
48
|
+
description: 'Execute test suite',
|
|
49
|
+
activeForm: 'Running tests',
|
|
50
|
+
metadata: { priority: 'high' },
|
|
51
|
+
}) as ToolResponse;
|
|
52
|
+
const result = getTextFromResponse(response);
|
|
53
|
+
expect(result).toContain('Task #1 created successfully');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('TaskUpdate', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
taskCreate.call!({ subject: 'Task 1', description: 'First task' });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('updates task status', () => {
|
|
63
|
+
const response = taskUpdate.call!({
|
|
64
|
+
taskId: '1',
|
|
65
|
+
status: 'in_progress',
|
|
66
|
+
}) as ToolResponse;
|
|
67
|
+
const result = getTextFromResponse(response);
|
|
68
|
+
expect(result).toContain('Task #1 updated successfully');
|
|
69
|
+
|
|
70
|
+
// Verify the update by getting the task
|
|
71
|
+
const getResponse = taskGet.call!({ taskId: '1' }) as ToolResponse;
|
|
72
|
+
const getResult = getTextFromResponse(getResponse);
|
|
73
|
+
expect(getResult).toContain('Status: in_progress');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('updates task subject and description', () => {
|
|
77
|
+
taskUpdate.call!({
|
|
78
|
+
taskId: '1',
|
|
79
|
+
subject: 'Updated subject',
|
|
80
|
+
description: 'Updated description',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const getResponse = taskGet.call!({ taskId: '1' }) as ToolResponse;
|
|
84
|
+
const result = getTextFromResponse(getResponse);
|
|
85
|
+
expect(result).toContain('Updated subject');
|
|
86
|
+
expect(result).toContain('Updated description');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('merges metadata', () => {
|
|
90
|
+
taskUpdate.call!({
|
|
91
|
+
taskId: '1',
|
|
92
|
+
metadata: { key1: 'value1', key2: 'value2' },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
taskUpdate.call!({
|
|
96
|
+
taskId: '1',
|
|
97
|
+
metadata: { key2: 'updated', key3: 'value3' },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const getResponse = taskGet.call!({ taskId: '1' }) as ToolResponse;
|
|
101
|
+
const result = getTextFromResponse(getResponse);
|
|
102
|
+
expect(result).toContain('key1');
|
|
103
|
+
expect(result).toContain('value1');
|
|
104
|
+
expect(result).toContain('key2');
|
|
105
|
+
expect(result).toContain('updated');
|
|
106
|
+
expect(result).toContain('key3');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('deletes metadata keys when set to null', () => {
|
|
110
|
+
taskUpdate.call!({
|
|
111
|
+
taskId: '1',
|
|
112
|
+
metadata: { key1: 'value1', key2: 'value2' },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
taskUpdate.call!({
|
|
116
|
+
taskId: '1',
|
|
117
|
+
metadata: { key1: null },
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const getResponse = taskGet.call!({ taskId: '1' }) as ToolResponse;
|
|
121
|
+
const result = getTextFromResponse(getResponse);
|
|
122
|
+
expect(result).not.toContain('key1');
|
|
123
|
+
expect(result).toContain('key2');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('adds task dependencies', () => {
|
|
127
|
+
taskCreate.call!({ subject: 'Task 2', description: 'Second task' });
|
|
128
|
+
|
|
129
|
+
taskUpdate.call!({
|
|
130
|
+
taskId: '1',
|
|
131
|
+
addBlocks: ['2'],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
taskUpdate.call!({
|
|
135
|
+
taskId: '2',
|
|
136
|
+
addBlockedBy: ['1'],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const getResponse = taskGet.call!({ taskId: '1' }) as ToolResponse;
|
|
140
|
+
const result = getTextFromResponse(getResponse);
|
|
141
|
+
expect(result).toContain('Blocks: 2');
|
|
142
|
+
|
|
143
|
+
const getResponse2 = taskGet.call!({ taskId: '2' }) as ToolResponse;
|
|
144
|
+
const result2 = getTextFromResponse(getResponse2);
|
|
145
|
+
expect(result2).toContain('Blocked By: 1');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('deduplicates dependencies', () => {
|
|
149
|
+
taskCreate.call!({ subject: 'Task 2', description: 'Second task' });
|
|
150
|
+
|
|
151
|
+
taskUpdate.call!({
|
|
152
|
+
taskId: '1',
|
|
153
|
+
addBlocks: ['2'],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
taskUpdate.call!({
|
|
157
|
+
taskId: '1',
|
|
158
|
+
addBlocks: ['2'],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const getResponse = taskGet.call!({ taskId: '1' }) as ToolResponse;
|
|
162
|
+
const result = getTextFromResponse(getResponse);
|
|
163
|
+
// Should only appear once
|
|
164
|
+
expect(result.match(/Blocks: 2/g)?.length).toBe(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('throws on non-existent task', () => {
|
|
168
|
+
expect(() => taskUpdate.call!({ taskId: '999', status: 'completed' })).toThrow(
|
|
169
|
+
'Task not found: 999'
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('throws when adding non-existent dependency', () => {
|
|
174
|
+
expect(() => taskUpdate.call!({ taskId: '1', addBlocks: ['999'] })).toThrow(
|
|
175
|
+
'Cannot add dependency: task 999 does not exist'
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('deletes task when status is deleted', () => {
|
|
180
|
+
const response = taskUpdate.call!({
|
|
181
|
+
taskId: '1',
|
|
182
|
+
status: 'deleted',
|
|
183
|
+
}) as ToolResponse;
|
|
184
|
+
const result = getTextFromResponse(response);
|
|
185
|
+
expect(result).toContain('Task #1 deleted successfully');
|
|
186
|
+
|
|
187
|
+
// Verify task is gone
|
|
188
|
+
expect(() => taskGet.call!({ taskId: '1' })).toThrow('Task not found: 1');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('TaskGet', () => {
|
|
193
|
+
it('returns full task details', () => {
|
|
194
|
+
taskCreate.call!({
|
|
195
|
+
subject: 'Test task',
|
|
196
|
+
description: 'Test description',
|
|
197
|
+
activeForm: 'Testing',
|
|
198
|
+
metadata: { priority: 'high' },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const response = taskGet.call!({ taskId: '1' }) as ToolResponse;
|
|
202
|
+
const result = getTextFromResponse(response);
|
|
203
|
+
|
|
204
|
+
expect(result).toContain('Task #1: Test task');
|
|
205
|
+
expect(result).toContain('Status: pending');
|
|
206
|
+
expect(result).toContain('Description: Test description');
|
|
207
|
+
expect(result).toContain('Active Form: Testing');
|
|
208
|
+
expect(result).toContain('priority');
|
|
209
|
+
expect(result).toContain('high');
|
|
210
|
+
expect(result).toContain('Created:');
|
|
211
|
+
expect(result).toContain('Updated:');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('throws on non-existent task', () => {
|
|
215
|
+
expect(() => taskGet.call!({ taskId: '999' })).toThrow('Task not found: 999');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('TaskList', () => {
|
|
220
|
+
it('returns empty message when no tasks exist', () => {
|
|
221
|
+
const response = taskList.call!({}) as ToolResponse;
|
|
222
|
+
const result = getTextFromResponse(response);
|
|
223
|
+
expect(result).toBe('No active tasks found');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('lists pending and in_progress tasks', () => {
|
|
227
|
+
taskCreate.call!({ subject: 'Task 1', description: 'First' });
|
|
228
|
+
taskCreate.call!({ subject: 'Task 2', description: 'Second' });
|
|
229
|
+
taskUpdate.call!({ taskId: '2', status: 'in_progress' });
|
|
230
|
+
|
|
231
|
+
const response = taskList.call!({}) as ToolResponse;
|
|
232
|
+
const result = getTextFromResponse(response);
|
|
233
|
+
|
|
234
|
+
expect(result).toContain('#1 [pending] Task 1');
|
|
235
|
+
expect(result).toContain('#2 [in_progress] Task 2');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('filters out completed tasks', () => {
|
|
239
|
+
taskCreate.call!({ subject: 'Task 1', description: 'First' });
|
|
240
|
+
taskCreate.call!({ subject: 'Task 2', description: 'Second' });
|
|
241
|
+
taskUpdate.call!({ taskId: '1', status: 'completed' });
|
|
242
|
+
|
|
243
|
+
const response = taskList.call!({}) as ToolResponse;
|
|
244
|
+
const result = getTextFromResponse(response);
|
|
245
|
+
|
|
246
|
+
expect(result).not.toContain('Task 1');
|
|
247
|
+
expect(result).toContain('#2 [pending] Task 2');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('filters out deleted tasks', () => {
|
|
251
|
+
taskCreate.call!({ subject: 'Task 1', description: 'First' });
|
|
252
|
+
taskCreate.call!({ subject: 'Task 2', description: 'Second' });
|
|
253
|
+
taskUpdate.call!({ taskId: '1', status: 'deleted' });
|
|
254
|
+
|
|
255
|
+
const response = taskList.call!({}) as ToolResponse;
|
|
256
|
+
const result = getTextFromResponse(response);
|
|
257
|
+
|
|
258
|
+
expect(result).not.toContain('Task 1');
|
|
259
|
+
expect(result).toContain('#2 [pending] Task 2');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('shows blocked tasks with dependencies', () => {
|
|
263
|
+
taskCreate.call!({ subject: 'Task 1', description: 'First' });
|
|
264
|
+
taskCreate.call!({ subject: 'Task 2', description: 'Second' });
|
|
265
|
+
taskUpdate.call!({ taskId: '2', addBlockedBy: ['1'] });
|
|
266
|
+
|
|
267
|
+
const response = taskList.call!({}) as ToolResponse;
|
|
268
|
+
const result = getTextFromResponse(response);
|
|
269
|
+
|
|
270
|
+
expect(result).toContain('#2 [pending] Task 2 (blocked by: #1)');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('sorts tasks by ID', () => {
|
|
274
|
+
taskCreate.call!({ subject: 'Task 1', description: 'First' });
|
|
275
|
+
taskCreate.call!({ subject: 'Task 2', description: 'Second' });
|
|
276
|
+
taskCreate.call!({ subject: 'Task 3', description: 'Third' });
|
|
277
|
+
|
|
278
|
+
const response = taskList.call!({}) as ToolResponse;
|
|
279
|
+
const result = getTextFromResponse(response);
|
|
280
|
+
|
|
281
|
+
const lines = result.split('\n');
|
|
282
|
+
expect(lines[0]).toContain('#1');
|
|
283
|
+
expect(lines[1]).toContain('#2');
|
|
284
|
+
expect(lines[2]).toContain('#3');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('returns empty when all tasks are completed', () => {
|
|
288
|
+
taskCreate.call!({ subject: 'Task 1', description: 'First' });
|
|
289
|
+
taskCreate.call!({ subject: 'Task 2', description: 'Second' });
|
|
290
|
+
taskUpdate.call!({ taskId: '1', status: 'completed' });
|
|
291
|
+
taskUpdate.call!({ taskId: '2', status: 'completed' });
|
|
292
|
+
|
|
293
|
+
const response = taskList.call!({}) as ToolResponse;
|
|
294
|
+
const result = getTextFromResponse(response);
|
|
295
|
+
|
|
296
|
+
expect(result).toBe('No active tasks found');
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
});
|
package/src/tool/task.ts
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
import { createToolResponse, ToolResponse } from './response';
|
|
4
|
+
|
|
5
|
+
// Task type definitions
|
|
6
|
+
export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'deleted';
|
|
7
|
+
|
|
8
|
+
export interface Task {
|
|
9
|
+
id: string;
|
|
10
|
+
subject: string;
|
|
11
|
+
description: string;
|
|
12
|
+
status: TaskStatus;
|
|
13
|
+
activeForm?: string;
|
|
14
|
+
owner?: string;
|
|
15
|
+
metadata?: Record<string, unknown>;
|
|
16
|
+
blocks: string[];
|
|
17
|
+
blockedBy: string[];
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Module-level storage
|
|
23
|
+
const taskStore = new Map<string, Task>();
|
|
24
|
+
let nextId = 1;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate a unique task ID
|
|
28
|
+
* @returns A unique task ID as a string
|
|
29
|
+
*/
|
|
30
|
+
function generateId(): string {
|
|
31
|
+
return String(nextId++);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reset task store for testing purposes
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export function _resetTaskStore(): void {
|
|
39
|
+
taskStore.clear();
|
|
40
|
+
nextId = 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Tool for creating tasks
|
|
45
|
+
* @returns A Tool object for creating tasks
|
|
46
|
+
*/
|
|
47
|
+
export function TaskCreate() {
|
|
48
|
+
return {
|
|
49
|
+
name: 'TaskCreate',
|
|
50
|
+
description: `Use this tool to create a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
|
|
51
|
+
It also helps the user understand the progress of the task and overall progress of their requests.
|
|
52
|
+
|
|
53
|
+
## When to Use This Tool
|
|
54
|
+
|
|
55
|
+
Use this tool proactively in these scenarios:
|
|
56
|
+
|
|
57
|
+
- Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
|
|
58
|
+
- Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
|
|
59
|
+
- User explicitly requests todo list - When the user directly asks you to use the todo list
|
|
60
|
+
- User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
|
|
61
|
+
|
|
62
|
+
All tasks are created with status 'pending'.`,
|
|
63
|
+
inputSchema: z.object({
|
|
64
|
+
subject: z
|
|
65
|
+
.string()
|
|
66
|
+
.describe(
|
|
67
|
+
'A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")'
|
|
68
|
+
),
|
|
69
|
+
description: z
|
|
70
|
+
.string()
|
|
71
|
+
.describe(
|
|
72
|
+
'Detailed description of what needs to be done, including context and acceptance criteria'
|
|
73
|
+
),
|
|
74
|
+
activeForm: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe(
|
|
78
|
+
'Present continuous form shown in the spinner when the task is in_progress (e.g., "Fixing authentication bug"). If omitted, the spinner shows the subject instead.'
|
|
79
|
+
),
|
|
80
|
+
metadata: z
|
|
81
|
+
.record(z.string(), z.unknown())
|
|
82
|
+
.optional()
|
|
83
|
+
.describe('Arbitrary metadata to attach to the task'),
|
|
84
|
+
}),
|
|
85
|
+
requireUserConfirm: false,
|
|
86
|
+
|
|
87
|
+
call({
|
|
88
|
+
subject,
|
|
89
|
+
description,
|
|
90
|
+
activeForm,
|
|
91
|
+
metadata,
|
|
92
|
+
}: {
|
|
93
|
+
subject: string;
|
|
94
|
+
description: string;
|
|
95
|
+
activeForm?: string;
|
|
96
|
+
metadata?: Record<string, unknown>;
|
|
97
|
+
}): ToolResponse {
|
|
98
|
+
const id = generateId();
|
|
99
|
+
const now = new Date().toISOString();
|
|
100
|
+
|
|
101
|
+
const task: Task = {
|
|
102
|
+
id,
|
|
103
|
+
subject,
|
|
104
|
+
description,
|
|
105
|
+
status: 'pending',
|
|
106
|
+
activeForm,
|
|
107
|
+
metadata,
|
|
108
|
+
blocks: [],
|
|
109
|
+
blockedBy: [],
|
|
110
|
+
createdAt: now,
|
|
111
|
+
updatedAt: now,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
taskStore.set(id, task);
|
|
115
|
+
|
|
116
|
+
return createToolResponse({
|
|
117
|
+
content: [
|
|
118
|
+
{
|
|
119
|
+
id: crypto.randomUUID(),
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: `Task #${id} created successfully: ${subject}`,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
state: 'success',
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Tool for updating tasks
|
|
132
|
+
* @returns A Tool object for updating tasks
|
|
133
|
+
*/
|
|
134
|
+
export function TaskUpdate() {
|
|
135
|
+
return {
|
|
136
|
+
name: 'TaskUpdate',
|
|
137
|
+
description: `Use this tool to update a task in the task list.
|
|
138
|
+
|
|
139
|
+
## When to Use This Tool
|
|
140
|
+
|
|
141
|
+
**Mark tasks as resolved:**
|
|
142
|
+
- When you have completed the work described in a task
|
|
143
|
+
- When a task is no longer needed or has been superseded
|
|
144
|
+
- IMPORTANT: Always mark your assigned tasks as resolved when you finish them
|
|
145
|
+
|
|
146
|
+
**Delete tasks:**
|
|
147
|
+
- When a task is no longer relevant or was created in error
|
|
148
|
+
- Setting status to 'deleted' permanently removes the task
|
|
149
|
+
|
|
150
|
+
**Update task details:**
|
|
151
|
+
- When requirements change or become clearer
|
|
152
|
+
- When establishing dependencies between tasks`,
|
|
153
|
+
inputSchema: z.object({
|
|
154
|
+
taskId: z.string().describe('The ID of the task to update'),
|
|
155
|
+
status: z
|
|
156
|
+
.enum(['pending', 'in_progress', 'completed', 'deleted'])
|
|
157
|
+
.optional()
|
|
158
|
+
.describe('New status for the task'),
|
|
159
|
+
subject: z.string().optional().describe('New subject for the task'),
|
|
160
|
+
description: z.string().optional().describe('New description for the task'),
|
|
161
|
+
activeForm: z
|
|
162
|
+
.string()
|
|
163
|
+
.optional()
|
|
164
|
+
.describe(
|
|
165
|
+
'Present continuous form shown in spinner when in_progress (e.g., "Running tests")'
|
|
166
|
+
),
|
|
167
|
+
owner: z.string().optional().describe('New owner for the task'),
|
|
168
|
+
metadata: z
|
|
169
|
+
.record(z.string(), z.unknown())
|
|
170
|
+
.optional()
|
|
171
|
+
.describe('Metadata keys to merge into the task. Set a key to null to delete it.'),
|
|
172
|
+
addBlocks: z.array(z.string()).optional().describe('Task IDs that this task blocks'),
|
|
173
|
+
addBlockedBy: z.array(z.string()).optional().describe('Task IDs that block this task'),
|
|
174
|
+
}),
|
|
175
|
+
requireUserConfirm: false,
|
|
176
|
+
|
|
177
|
+
call({
|
|
178
|
+
taskId,
|
|
179
|
+
status,
|
|
180
|
+
subject,
|
|
181
|
+
description,
|
|
182
|
+
activeForm,
|
|
183
|
+
owner,
|
|
184
|
+
metadata,
|
|
185
|
+
addBlocks,
|
|
186
|
+
addBlockedBy,
|
|
187
|
+
}: {
|
|
188
|
+
taskId: string;
|
|
189
|
+
status?: TaskStatus;
|
|
190
|
+
subject?: string;
|
|
191
|
+
description?: string;
|
|
192
|
+
activeForm?: string;
|
|
193
|
+
owner?: string;
|
|
194
|
+
metadata?: Record<string, unknown>;
|
|
195
|
+
addBlocks?: string[];
|
|
196
|
+
addBlockedBy?: string[];
|
|
197
|
+
}): ToolResponse {
|
|
198
|
+
const task = taskStore.get(taskId);
|
|
199
|
+
if (!task) {
|
|
200
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Validate dependencies exist
|
|
204
|
+
if (addBlocks) {
|
|
205
|
+
for (const depId of addBlocks) {
|
|
206
|
+
if (!taskStore.has(depId)) {
|
|
207
|
+
throw new Error(`Cannot add dependency: task ${depId} does not exist`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (addBlockedBy) {
|
|
212
|
+
for (const depId of addBlockedBy) {
|
|
213
|
+
if (!taskStore.has(depId)) {
|
|
214
|
+
throw new Error(`Cannot add dependency: task ${depId} does not exist`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Update fields
|
|
220
|
+
if (subject !== undefined) task.subject = subject;
|
|
221
|
+
if (description !== undefined) task.description = description;
|
|
222
|
+
if (activeForm !== undefined) task.activeForm = activeForm;
|
|
223
|
+
if (owner !== undefined) task.owner = owner;
|
|
224
|
+
|
|
225
|
+
// Merge metadata
|
|
226
|
+
if (metadata !== undefined) {
|
|
227
|
+
if (!task.metadata) {
|
|
228
|
+
task.metadata = {};
|
|
229
|
+
}
|
|
230
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
231
|
+
if (value === null) {
|
|
232
|
+
delete task.metadata[key];
|
|
233
|
+
} else {
|
|
234
|
+
task.metadata[key] = value;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Add dependencies with deduplication
|
|
240
|
+
if (addBlocks) {
|
|
241
|
+
task.blocks = [...new Set([...task.blocks, ...addBlocks])];
|
|
242
|
+
}
|
|
243
|
+
if (addBlockedBy) {
|
|
244
|
+
task.blockedBy = [...new Set([...task.blockedBy, ...addBlockedBy])];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
task.updatedAt = new Date().toISOString();
|
|
248
|
+
|
|
249
|
+
// Handle status change
|
|
250
|
+
if (status !== undefined) {
|
|
251
|
+
if (status === 'deleted') {
|
|
252
|
+
taskStore.delete(taskId);
|
|
253
|
+
return createToolResponse({
|
|
254
|
+
content: [
|
|
255
|
+
{
|
|
256
|
+
id: crypto.randomUUID(),
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: `Task #${taskId} deleted successfully`,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
state: 'success',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
task.status = status;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return createToolResponse({
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
id: crypto.randomUUID(),
|
|
271
|
+
type: 'text',
|
|
272
|
+
text: `Task #${taskId} updated successfully`,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
state: 'success',
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Tool for retrieving a single task
|
|
283
|
+
* @returns A Tool object for retrieving a task by ID
|
|
284
|
+
*/
|
|
285
|
+
export function TaskGet() {
|
|
286
|
+
return {
|
|
287
|
+
name: 'TaskGet',
|
|
288
|
+
description: `Use this tool to retrieve a task by its ID from the task list.
|
|
289
|
+
|
|
290
|
+
## When to Use This Tool
|
|
291
|
+
|
|
292
|
+
- When you need the full description and context before starting work on a task
|
|
293
|
+
- To understand task dependencies (what it blocks, what blocks it)
|
|
294
|
+
- After being assigned a task, to get complete requirements`,
|
|
295
|
+
inputSchema: z.object({
|
|
296
|
+
taskId: z.string().describe('The ID of the task to retrieve'),
|
|
297
|
+
}),
|
|
298
|
+
requireUserConfirm: false,
|
|
299
|
+
|
|
300
|
+
call({ taskId }: { taskId: string }): ToolResponse {
|
|
301
|
+
const task = taskStore.get(taskId);
|
|
302
|
+
if (!task) {
|
|
303
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
let text = `Task #${task.id}: ${task.subject}\n`;
|
|
307
|
+
text += `Status: ${task.status}\n`;
|
|
308
|
+
text += `Description: ${task.description}\n`;
|
|
309
|
+
|
|
310
|
+
if (task.activeForm) {
|
|
311
|
+
text += `Active Form: ${task.activeForm}\n`;
|
|
312
|
+
}
|
|
313
|
+
if (task.owner) {
|
|
314
|
+
text += `Owner: ${task.owner}\n`;
|
|
315
|
+
}
|
|
316
|
+
if (task.blocks.length > 0) {
|
|
317
|
+
text += `Blocks: ${task.blocks.join(', ')}\n`;
|
|
318
|
+
}
|
|
319
|
+
if (task.blockedBy.length > 0) {
|
|
320
|
+
text += `Blocked By: ${task.blockedBy.join(', ')}\n`;
|
|
321
|
+
}
|
|
322
|
+
if (task.metadata && Object.keys(task.metadata).length > 0) {
|
|
323
|
+
text += `Metadata: ${JSON.stringify(task.metadata, null, 2)}\n`;
|
|
324
|
+
}
|
|
325
|
+
text += `Created: ${task.createdAt}\n`;
|
|
326
|
+
text += `Updated: ${task.updatedAt}`;
|
|
327
|
+
|
|
328
|
+
return createToolResponse({
|
|
329
|
+
content: [
|
|
330
|
+
{
|
|
331
|
+
id: crypto.randomUUID(),
|
|
332
|
+
type: 'text',
|
|
333
|
+
text,
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
state: 'success',
|
|
337
|
+
});
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Tool for listing all active tasks
|
|
344
|
+
* @returns A Tool object for listing all active tasks
|
|
345
|
+
*/
|
|
346
|
+
export function TaskList() {
|
|
347
|
+
return {
|
|
348
|
+
name: 'TaskList',
|
|
349
|
+
description: `Use this tool to list all tasks in the task list.
|
|
350
|
+
|
|
351
|
+
## When to Use This Tool
|
|
352
|
+
|
|
353
|
+
- To see what tasks are available to work on (status: 'pending', no owner, not blocked)
|
|
354
|
+
- To check overall progress on the project
|
|
355
|
+
- To find tasks that are blocked and need dependencies resolved
|
|
356
|
+
- After completing a task, to check for newly unblocked work or claim the next available task`,
|
|
357
|
+
inputSchema: z.object({}),
|
|
358
|
+
requireUserConfirm: false,
|
|
359
|
+
|
|
360
|
+
call(): ToolResponse {
|
|
361
|
+
// Filter to only pending and in_progress tasks
|
|
362
|
+
const activeTasks = Array.from(taskStore.values())
|
|
363
|
+
.filter(task => task.status === 'pending' || task.status === 'in_progress')
|
|
364
|
+
.sort((a, b) => Number(a.id) - Number(b.id));
|
|
365
|
+
|
|
366
|
+
if (activeTasks.length === 0) {
|
|
367
|
+
return createToolResponse({
|
|
368
|
+
content: [
|
|
369
|
+
{
|
|
370
|
+
id: crypto.randomUUID(),
|
|
371
|
+
type: 'text',
|
|
372
|
+
text: 'No active tasks found',
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
state: 'success',
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const lines = activeTasks.map(task => {
|
|
380
|
+
let line = `#${task.id} [${task.status}] ${task.subject}`;
|
|
381
|
+
if (task.blockedBy.length > 0) {
|
|
382
|
+
line += ` (blocked by: #${task.blockedBy.join(', #')})`;
|
|
383
|
+
}
|
|
384
|
+
return line;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
return createToolResponse({
|
|
388
|
+
content: [
|
|
389
|
+
{
|
|
390
|
+
id: crypto.randomUUID(),
|
|
391
|
+
type: 'text',
|
|
392
|
+
text: lines.join('\n'),
|
|
393
|
+
},
|
|
394
|
+
],
|
|
395
|
+
state: 'success',
|
|
396
|
+
});
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
}
|