@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.
Files changed (136) hide show
  1. package/dist/agent/index.d.mts +234 -0
  2. package/dist/agent/index.d.ts +234 -0
  3. package/dist/agent/index.js +1412 -0
  4. package/dist/agent/index.js.map +1 -0
  5. package/dist/agent/index.mjs +1375 -0
  6. package/dist/agent/index.mjs.map +1 -0
  7. package/dist/base-BOx3UzOl.d.mts +41 -0
  8. package/dist/base-BoIps2RL.d.ts +41 -0
  9. package/dist/base-C7jwyH4Z.d.mts +52 -0
  10. package/dist/base-Cwi4bjze.d.ts +127 -0
  11. package/dist/base-DYlBMCy_.d.mts +127 -0
  12. package/dist/base-NX-knWOv.d.ts +52 -0
  13. package/dist/block-VsnHrllL.d.mts +48 -0
  14. package/dist/block-VsnHrllL.d.ts +48 -0
  15. package/dist/event/index.d.mts +181 -0
  16. package/dist/event/index.d.ts +181 -0
  17. package/dist/event/index.js +58 -0
  18. package/dist/event/index.js.map +1 -0
  19. package/dist/event/index.mjs +33 -0
  20. package/dist/event/index.mjs.map +1 -0
  21. package/dist/formatter/index.d.mts +187 -0
  22. package/dist/formatter/index.d.ts +187 -0
  23. package/dist/formatter/index.js +647 -0
  24. package/dist/formatter/index.js.map +1 -0
  25. package/dist/formatter/index.mjs +616 -0
  26. package/dist/formatter/index.mjs.map +1 -0
  27. package/dist/index-BTJDlKvQ.d.mts +195 -0
  28. package/dist/index-BcatlwXQ.d.ts +195 -0
  29. package/dist/index-CAxQAkiP.d.mts +21 -0
  30. package/dist/index-CAxQAkiP.d.ts +21 -0
  31. package/dist/mcp/index.d.mts +9 -0
  32. package/dist/mcp/index.d.ts +9 -0
  33. package/dist/mcp/index.js +432 -0
  34. package/dist/mcp/index.js.map +1 -0
  35. package/dist/mcp/index.mjs +408 -0
  36. package/dist/mcp/index.mjs.map +1 -0
  37. package/dist/message/index.d.mts +10 -0
  38. package/dist/message/index.d.ts +10 -0
  39. package/dist/message/index.js +67 -0
  40. package/dist/message/index.js.map +1 -0
  41. package/dist/message/index.mjs +37 -0
  42. package/dist/message/index.mjs.map +1 -0
  43. package/dist/message-CkN21KaY.d.mts +99 -0
  44. package/dist/message-CzLeTlua.d.ts +99 -0
  45. package/dist/model/index.d.mts +377 -0
  46. package/dist/model/index.d.ts +377 -0
  47. package/dist/model/index.js +1880 -0
  48. package/dist/model/index.js.map +1 -0
  49. package/dist/model/index.mjs +1849 -0
  50. package/dist/model/index.mjs.map +1 -0
  51. package/dist/storage/index.d.mts +68 -0
  52. package/dist/storage/index.d.ts +68 -0
  53. package/dist/storage/index.js +250 -0
  54. package/dist/storage/index.js.map +1 -0
  55. package/dist/storage/index.mjs +212 -0
  56. package/dist/storage/index.mjs.map +1 -0
  57. package/dist/tool/index.d.mts +311 -0
  58. package/dist/tool/index.d.ts +311 -0
  59. package/dist/tool/index.js +1494 -0
  60. package/dist/tool/index.js.map +1 -0
  61. package/dist/tool/index.mjs +1447 -0
  62. package/dist/tool/index.mjs.map +1 -0
  63. package/dist/toolkit-CEpulFi0.d.ts +99 -0
  64. package/dist/toolkit-CGEZSZPa.d.mts +99 -0
  65. package/jest.config.js +11 -0
  66. package/package.json +92 -0
  67. package/src/_utils/common.ts +104 -0
  68. package/src/_utils/index.ts +1 -0
  69. package/src/agent/agent-base.ts +0 -0
  70. package/src/agent/agent.test.ts +1028 -0
  71. package/src/agent/agent.ts +1032 -0
  72. package/src/agent/index.ts +2 -0
  73. package/src/agent/interfaces.ts +23 -0
  74. package/src/agent/test-compression.ts +72 -0
  75. package/src/event/index.ts +250 -0
  76. package/src/formatter/base.ts +133 -0
  77. package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
  78. package/src/formatter/dashscope-chat-formatter.ts +163 -0
  79. package/src/formatter/deepseek-chat-formatter.ts +130 -0
  80. package/src/formatter/index.ts +5 -0
  81. package/src/formatter/ollama-chat-formatter.ts +67 -0
  82. package/src/formatter/openai-chat-formatter.test.ts +263 -0
  83. package/src/formatter/openai-chat-formatter.ts +301 -0
  84. package/src/formatter/openai.md +767 -0
  85. package/src/mcp/base.ts +114 -0
  86. package/src/mcp/http.test.ts +303 -0
  87. package/src/mcp/http.ts +224 -0
  88. package/src/mcp/index.ts +2 -0
  89. package/src/mcp/stdio.test.ts +91 -0
  90. package/src/mcp/stdio.ts +119 -0
  91. package/src/message/block.ts +60 -0
  92. package/src/message/enums.ts +4 -0
  93. package/src/message/index.ts +12 -0
  94. package/src/message/message.test.ts +80 -0
  95. package/src/message/message.ts +131 -0
  96. package/src/model/base.ts +226 -0
  97. package/src/model/dashscope-model.test.ts +335 -0
  98. package/src/model/dashscope-model.ts +441 -0
  99. package/src/model/deepseek-model.test.ts +279 -0
  100. package/src/model/deepseek-model.ts +401 -0
  101. package/src/model/index.ts +7 -0
  102. package/src/model/ollama-model.test.ts +307 -0
  103. package/src/model/ollama-model.ts +356 -0
  104. package/src/model/openai-model.ts +327 -0
  105. package/src/model/response.ts +22 -0
  106. package/src/model/usage.ts +12 -0
  107. package/src/storage/base.ts +52 -0
  108. package/src/storage/file-system.test.ts +587 -0
  109. package/src/storage/file-system.ts +269 -0
  110. package/src/storage/index.ts +2 -0
  111. package/src/tool/base.ts +23 -0
  112. package/src/tool/bash.test.ts +174 -0
  113. package/src/tool/bash.ts +152 -0
  114. package/src/tool/edit.test.ts +83 -0
  115. package/src/tool/edit.ts +95 -0
  116. package/src/tool/glob.test.ts +63 -0
  117. package/src/tool/glob.ts +166 -0
  118. package/src/tool/grep.test.ts +74 -0
  119. package/src/tool/grep.ts +256 -0
  120. package/src/tool/index.ts +10 -0
  121. package/src/tool/read.test.ts +77 -0
  122. package/src/tool/read.ts +117 -0
  123. package/src/tool/response.ts +82 -0
  124. package/src/tool/task.test.ts +299 -0
  125. package/src/tool/task.ts +399 -0
  126. package/src/tool/toolkit.test.ts +636 -0
  127. package/src/tool/toolkit.ts +601 -0
  128. package/src/tool/write.test.ts +52 -0
  129. package/src/tool/write.ts +57 -0
  130. package/src/type/index.ts +52 -0
  131. package/tsconfig.build.json +4 -0
  132. package/tsconfig.cjs.json +11 -0
  133. package/tsconfig.esm.json +10 -0
  134. package/tsconfig.json +14 -0
  135. package/tsup.config.ts +20 -0
  136. 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
+ });
@@ -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
+ }