onbuzz 3.6.1 → 3.6.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 (83) hide show
  1. package/package.json +1 -1
  2. package/src/__test-utils__/fixtures/malformedJson.js +31 -0
  3. package/src/__test-utils__/globalSetup.js +9 -0
  4. package/src/__test-utils__/globalTeardown.js +12 -0
  5. package/src/__test-utils__/mockFactories.js +101 -0
  6. package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -0
  7. package/src/analyzers/__tests__/ConfigValidator.test.js +362 -0
  8. package/src/analyzers/__tests__/ESLintAnalyzer.test.js +271 -0
  9. package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -0
  10. package/src/analyzers/__tests__/PrettierFormatter.test.js +197 -0
  11. package/src/analyzers/__tests__/PythonAnalyzer.test.js +208 -0
  12. package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -0
  13. package/src/analyzers/__tests__/SparrowAnalyzer.test.js +270 -0
  14. package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -0
  15. package/src/core/__tests__/agentPool.test.js +601 -0
  16. package/src/core/__tests__/agentScheduler.test.js +576 -0
  17. package/src/core/__tests__/contextManager.test.js +252 -0
  18. package/src/core/__tests__/flowExecutor.test.js +262 -0
  19. package/src/core/__tests__/messageProcessor.test.js +627 -0
  20. package/src/core/__tests__/orchestrator.test.js +257 -0
  21. package/src/core/__tests__/stateManager.test.js +375 -0
  22. package/src/core/agentPool.js +11 -1
  23. package/src/index.js +25 -9
  24. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +3 -5
  25. package/src/services/__tests__/agentActivityService.test.js +319 -0
  26. package/src/services/__tests__/apiKeyManager.test.js +206 -0
  27. package/src/services/__tests__/benchmarkService.test.js +184 -0
  28. package/src/services/__tests__/budgetService.test.js +211 -0
  29. package/src/services/__tests__/contextInjectionService.test.js +205 -0
  30. package/src/services/__tests__/conversationCompactionService.test.js +280 -0
  31. package/src/services/__tests__/credentialVault.test.js +469 -0
  32. package/src/services/__tests__/errorHandler.test.js +314 -0
  33. package/src/services/__tests__/fileAttachmentService.test.js +278 -0
  34. package/src/services/__tests__/flowContextService.test.js +199 -0
  35. package/src/services/__tests__/memoryService.test.js +450 -0
  36. package/src/services/__tests__/modelRouterService.test.js +388 -0
  37. package/src/services/__tests__/modelsService.test.js +261 -0
  38. package/src/services/__tests__/portRegistry.test.js +123 -0
  39. package/src/services/__tests__/projectDetector.test.js +34 -0
  40. package/src/services/__tests__/promptService.test.js +242 -0
  41. package/src/services/__tests__/qualityInspector.test.js +97 -0
  42. package/src/services/__tests__/scheduleService.test.js +308 -0
  43. package/src/services/__tests__/serviceRegistry.test.js +74 -0
  44. package/src/services/__tests__/skillsService.test.js +402 -0
  45. package/src/services/__tests__/tokenCountingService.test.js +48 -0
  46. package/src/tools/__tests__/agentCommunicationTool.test.js +500 -0
  47. package/src/tools/__tests__/agentDelayTool.test.js +342 -0
  48. package/src/tools/__tests__/asyncToolManager.test.js +344 -0
  49. package/src/tools/__tests__/baseTool.test.js +420 -0
  50. package/src/tools/__tests__/codeMapTool.test.js +348 -0
  51. package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -0
  52. package/src/tools/__tests__/fileTreeTool.test.js +274 -0
  53. package/src/tools/__tests__/filesystemTool.test.js +717 -0
  54. package/src/tools/__tests__/helpTool.test.js +204 -0
  55. package/src/tools/__tests__/jobDoneTool.test.js +296 -0
  56. package/src/tools/__tests__/memoryTool.test.js +297 -0
  57. package/src/tools/__tests__/seekTool.test.js +282 -0
  58. package/src/tools/__tests__/skillsTool.test.js +226 -0
  59. package/src/tools/__tests__/staticAnalysisTool.test.js +509 -0
  60. package/src/tools/__tests__/taskManagerTool.test.js +725 -0
  61. package/src/tools/__tests__/terminalTool.test.js +384 -0
  62. package/src/tools/__tests__/userPromptTool.test.js +297 -0
  63. package/src/tools/__tests__/webTool.e2e.test.js +25 -11
  64. package/src/tools/webTool.js +6 -12
  65. package/src/types/__tests__/agent.test.js +499 -0
  66. package/src/types/__tests__/contextReference.test.js +606 -0
  67. package/src/types/__tests__/conversation.test.js +555 -0
  68. package/src/types/__tests__/toolCommand.test.js +584 -0
  69. package/src/types/contextReference.js +1 -1
  70. package/src/utilities/__tests__/attachmentValidator.test.js +80 -0
  71. package/src/utilities/__tests__/configManager.test.js +397 -0
  72. package/src/utilities/__tests__/constants.test.js +49 -0
  73. package/src/utilities/__tests__/directoryAccessManager.test.js +388 -0
  74. package/src/utilities/__tests__/fileProcessor.test.js +104 -0
  75. package/src/utilities/__tests__/jsonRepair.test.js +104 -0
  76. package/src/utilities/__tests__/logger.test.js +129 -0
  77. package/src/utilities/__tests__/platformUtils.test.js +87 -0
  78. package/src/utilities/__tests__/structuredFileValidator.test.js +263 -0
  79. package/src/utilities/__tests__/tagParser.test.js +887 -0
  80. package/src/utilities/__tests__/toolConstants.test.js +94 -0
  81. package/src/utilities/tagParser.js +2 -2
  82. package/src/tools/browserTool.js +0 -897
  83. package/src/utilities/platformUtils.test.js +0 -98
@@ -0,0 +1,725 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { createMockLogger, createMockConfig } from '../../__test-utils__/mockFactories.js';
3
+
4
+ // Mock uuid
5
+ let uuidCounter = 0;
6
+ jest.unstable_mockModule('uuid', () => ({
7
+ v4: jest.fn(() => `mock-uuid-${++uuidCounter}`)
8
+ }));
9
+
10
+ const { default: TaskManagerTool } = await import('../taskManagerTool.js');
11
+
12
+ /**
13
+ * Helper: create a tool instance, a fake agent, and context.
14
+ */
15
+ function createTestSetup() {
16
+ const logger = createMockLogger();
17
+ const tool = new TaskManagerTool({ description: 'test task manager' });
18
+ tool.logger = logger;
19
+
20
+ const agent = {
21
+ id: 'agent-1',
22
+ name: 'Test Agent',
23
+ lastActivity: null,
24
+ taskList: {
25
+ tasks: [],
26
+ lastUpdated: new Date().toISOString()
27
+ }
28
+ };
29
+
30
+ const agentPool = {
31
+ getAgent: jest.fn().mockResolvedValue(agent),
32
+ persistAgentState: jest.fn().mockResolvedValue(undefined)
33
+ };
34
+
35
+ const context = {
36
+ agentId: 'agent-1',
37
+ agentName: 'Test Agent',
38
+ agentPool,
39
+ projectDir: '/tmp/test'
40
+ };
41
+
42
+ return { tool, agent, agentPool, context, logger };
43
+ }
44
+
45
+ beforeEach(() => {
46
+ uuidCounter = 0;
47
+ });
48
+
49
+ describe('TaskManagerTool', () => {
50
+ // ── constructor ─────────────────────────────────────────────────
51
+ describe('constructor', () => {
52
+ test('initializes with supported actions, priorities, and statuses', () => {
53
+ const tool = new TaskManagerTool();
54
+ expect(tool.supportedActions).toContain('create');
55
+ expect(tool.supportedActions).toContain('sync');
56
+ expect(tool.supportedActions).toContain('analytics');
57
+ expect(tool.taskPriorities).toEqual(['urgent', 'high', 'medium', 'low']);
58
+ expect(tool.taskStatuses).toContain('pending');
59
+ expect(tool.taskStatuses).toContain('completed');
60
+ expect(tool.taskTemplates).toHaveProperty('bug-fix');
61
+ });
62
+ });
63
+
64
+ // ── getDescription ──────────────────────────────────────────────
65
+ describe('getDescription', () => {
66
+ test('returns a non-empty description string', () => {
67
+ const tool = new TaskManagerTool();
68
+ const desc = tool.getDescription();
69
+ expect(typeof desc).toBe('string');
70
+ expect(desc.length).toBeGreaterThan(50);
71
+ });
72
+ });
73
+
74
+ // ── parseParameters ─────────────────────────────────────────────
75
+ describe('parseParameters', () => {
76
+ test('returns rawContent for string input', () => {
77
+ const tool = new TaskManagerTool();
78
+ expect(tool.parseParameters('hello')).toEqual({ rawContent: 'hello' });
79
+ });
80
+
81
+ test('unwraps tag-parser format objects', () => {
82
+ const tool = new TaskManagerTool();
83
+ const result = tool.parseParameters({
84
+ action: { value: 'create', attributes: {} },
85
+ title: { value: 'My Task', attributes: {} }
86
+ });
87
+ expect(result.action).toBe('create');
88
+ expect(result.title).toBe('My Task');
89
+ });
90
+
91
+ test('passes through plain objects', () => {
92
+ const tool = new TaskManagerTool();
93
+ expect(tool.parseParameters({ action: 'list' })).toEqual({ action: 'list' });
94
+ });
95
+
96
+ test('returns non-object values as-is', () => {
97
+ const tool = new TaskManagerTool();
98
+ expect(tool.parseParameters(null)).toBeNull();
99
+ });
100
+ });
101
+
102
+ // ── create action ───────────────────────────────────────────────
103
+ describe('execute - create', () => {
104
+ test('creates a task with title, description, and priority', async () => {
105
+ const { tool, agent, context } = createTestSetup();
106
+ const result = await tool.execute(
107
+ { action: 'create', title: 'Build API', description: 'REST endpoints', priority: 'high' },
108
+ context
109
+ );
110
+ expect(result.success).toBe(true);
111
+ expect(result.action).toBe('create');
112
+ expect(result.result.task.title).toBe('Build API');
113
+ expect(result.result.task.priority).toBe('high');
114
+ expect(result.result.task.status).toBe('pending');
115
+ expect(agent.taskList.tasks).toHaveLength(1);
116
+ });
117
+
118
+ test('uses medium as default priority', async () => {
119
+ const { tool, context } = createTestSetup();
120
+ const result = await tool.execute({ action: 'create', title: 'Default' }, context);
121
+ expect(result.result.task.priority).toBe('medium');
122
+ });
123
+
124
+ test('errors when title is missing', async () => {
125
+ const { tool, context } = createTestSetup();
126
+ const result = await tool.execute({ action: 'create', priority: 'low' }, context);
127
+ expect(result.success).toBe(false);
128
+ expect(result.error).toContain('title is required');
129
+ });
130
+
131
+ test('errors for invalid priority', async () => {
132
+ const { tool, context } = createTestSetup();
133
+ const result = await tool.execute(
134
+ { action: 'create', title: 'Test', priority: 'superurgent' }, context
135
+ );
136
+ expect(result.success).toBe(false);
137
+ expect(result.error).toContain('Invalid priority');
138
+ });
139
+ });
140
+
141
+ // ── actions array format ────────────────────────────────────────
142
+ describe('execute - actions array format', () => {
143
+ test('unwraps first element of actions array', async () => {
144
+ const { tool, context } = createTestSetup();
145
+ const result = await tool.execute(
146
+ { actions: [{ type: 'create', title: 'From array', priority: 'medium' }] },
147
+ context
148
+ );
149
+ expect(result.success).toBe(true);
150
+ expect(result.result.task.title).toBe('From array');
151
+ });
152
+
153
+ test('deep-unwraps tag-parser wrapped values in actions array', async () => {
154
+ const { tool, context } = createTestSetup();
155
+ const result = await tool.execute({
156
+ actions: [{
157
+ type: { value: 'create', attributes: {} },
158
+ title: { value: 'Wrapped', attributes: {} },
159
+ priority: 'medium'
160
+ }]
161
+ }, context);
162
+ expect(result.success).toBe(true);
163
+ expect(result.result.task.title).toBe('Wrapped');
164
+ });
165
+ });
166
+
167
+ // ── update action ───────────────────────────────────────────────
168
+ describe('execute - update', () => {
169
+ test('updates task status and priority', async () => {
170
+ const { tool, agent, context } = createTestSetup();
171
+ await tool.execute({ action: 'create', title: 'Task A', priority: 'low' }, context);
172
+ const taskId = agent.taskList.tasks[0].id;
173
+ const result = await tool.execute(
174
+ { action: 'update', taskId, status: 'in_progress', priority: 'high' }, context
175
+ );
176
+ expect(result.success).toBe(true);
177
+ expect(result.result.task.status).toBe('in_progress');
178
+ expect(result.result.task.priority).toBe('high');
179
+ });
180
+
181
+ test('updates title and description', async () => {
182
+ const { tool, agent, context } = createTestSetup();
183
+ await tool.execute({ action: 'create', title: 'Old', priority: 'medium' }, context);
184
+ const taskId = agent.taskList.tasks[0].id;
185
+ const result = await tool.execute(
186
+ { action: 'update', taskId, title: 'New Title', description: 'Desc' }, context
187
+ );
188
+ expect(result.result.task.title).toBe('New Title');
189
+ expect(result.result.task.description).toBe('Desc');
190
+ });
191
+
192
+ test('errors when taskId is missing', async () => {
193
+ const { tool, context } = createTestSetup();
194
+ const result = await tool.execute({ action: 'update', status: 'completed' }, context);
195
+ expect(result.success).toBe(false);
196
+ expect(result.error).toContain('Task ID is required');
197
+ });
198
+
199
+ test('errors for non-existent task', async () => {
200
+ const { tool, context } = createTestSetup();
201
+ const result = await tool.execute({ action: 'update', taskId: 'no-such', status: 'completed' }, context);
202
+ expect(result.success).toBe(false);
203
+ expect(result.error).toContain('Task not found');
204
+ });
205
+
206
+ test('errors for invalid status', async () => {
207
+ const { tool, agent, context } = createTestSetup();
208
+ await tool.execute({ action: 'create', title: 'T', priority: 'medium' }, context);
209
+ const taskId = agent.taskList.tasks[0].id;
210
+ const result = await tool.execute({ action: 'update', taskId, status: 'badstatus' }, context);
211
+ expect(result.success).toBe(false);
212
+ expect(result.error).toContain('Invalid status');
213
+ });
214
+ });
215
+
216
+ // ── list action ─────────────────────────────────────────────────
217
+ describe('execute - list', () => {
218
+ test('lists all tasks with summary counts', async () => {
219
+ const { tool, context } = createTestSetup();
220
+ await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
221
+ await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
222
+ const result = await tool.execute({ action: 'list' }, context);
223
+ expect(result.success).toBe(true);
224
+ expect(result.result.totalTasks).toBe(2);
225
+ expect(result.result.summary.pending).toBe(2);
226
+ });
227
+
228
+ test('filters by status', async () => {
229
+ const { tool, agent, context } = createTestSetup();
230
+ await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
231
+ await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
232
+ await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
233
+ const result = await tool.execute({ action: 'list', status: 'pending' }, context);
234
+ expect(result.result.totalTasks).toBe(1);
235
+ });
236
+
237
+ test('filters by priority', async () => {
238
+ const { tool, context } = createTestSetup();
239
+ await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
240
+ await tool.execute({ action: 'create', title: 'T2', priority: 'low' }, context);
241
+ const result = await tool.execute({ action: 'list', priority: 'high' }, context);
242
+ expect(result.result.totalTasks).toBe(1);
243
+ });
244
+
245
+ test('sorts by priority then creation date', async () => {
246
+ const { tool, context } = createTestSetup();
247
+ await tool.execute({ action: 'create', title: 'Low', priority: 'low' }, context);
248
+ await tool.execute({ action: 'create', title: 'High', priority: 'high' }, context);
249
+ const result = await tool.execute({ action: 'list' }, context);
250
+ expect(result.result.tasks[0].title).toBe('High');
251
+ });
252
+ });
253
+
254
+ // ── complete action ─────────────────────────────────────────────
255
+ describe('execute - complete', () => {
256
+ test('marks task as completed with timestamp', async () => {
257
+ const { tool, agent, context } = createTestSetup();
258
+ await tool.execute({ action: 'create', title: 'Finish', priority: 'medium' }, context);
259
+ const taskId = agent.taskList.tasks[0].id;
260
+ const result = await tool.execute({ action: 'complete', taskId }, context);
261
+ expect(result.success).toBe(true);
262
+ expect(result.result.task.status).toBe('completed');
263
+ expect(result.result.task.completedAt).toBeDefined();
264
+ });
265
+
266
+ test('auto-completes first in-progress task when no taskId', async () => {
267
+ const { tool, agent, context } = createTestSetup();
268
+ await tool.execute({ action: 'create', title: 'Auto', priority: 'medium' }, context);
269
+ agent.taskList.tasks[0].status = 'in_progress';
270
+ const result = await tool.execute({ action: 'complete' }, context);
271
+ expect(result.result.task.status).toBe('completed');
272
+ });
273
+
274
+ test('returns message for already-completed task', async () => {
275
+ const { tool, agent, context } = createTestSetup();
276
+ await tool.execute({ action: 'create', title: 'Done', priority: 'medium' }, context);
277
+ const taskId = agent.taskList.tasks[0].id;
278
+ await tool.execute({ action: 'complete', taskId }, context);
279
+ const result = await tool.execute({ action: 'complete', taskId }, context);
280
+ expect(result.result.message).toContain('already completed');
281
+ });
282
+
283
+ test('sets TTL to 1 when no pending tasks remain', async () => {
284
+ const { tool, agent, context } = createTestSetup();
285
+ await tool.execute({ action: 'create', title: 'Only', priority: 'medium' }, context);
286
+ await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
287
+ expect(agent.ttl).toBe(1);
288
+ });
289
+
290
+ test('includes no-tasks hint in summary', async () => {
291
+ const { tool, agent, context } = createTestSetup();
292
+ await tool.execute({ action: 'create', title: 'Only', priority: 'medium' }, context);
293
+ const result = await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
294
+ expect(result.summary).toContain('No remaining tasks');
295
+ });
296
+
297
+ test('errors when no taskId and no in-progress tasks', async () => {
298
+ const { tool, context } = createTestSetup();
299
+ const result = await tool.execute({ action: 'complete' }, context);
300
+ expect(result.success).toBe(false);
301
+ expect(result.error).toContain('No task ID provided');
302
+ });
303
+ });
304
+
305
+ // ── cancel action ───────────────────────────────────────────────
306
+ describe('execute - cancel', () => {
307
+ test('cancels a task with reason', async () => {
308
+ const { tool, agent, context } = createTestSetup();
309
+ await tool.execute({ action: 'create', title: 'Cancel me', priority: 'medium' }, context);
310
+ const taskId = agent.taskList.tasks[0].id;
311
+ const result = await tool.execute({ action: 'cancel', taskId, reason: 'Not needed' }, context);
312
+ expect(result.success).toBe(true);
313
+ expect(result.result.task.status).toBe('cancelled');
314
+ expect(result.result.task.cancellationReason).toBe('Not needed');
315
+ });
316
+
317
+ test('errors when taskId is missing', async () => {
318
+ const { tool, context } = createTestSetup();
319
+ const result = await tool.execute({ action: 'cancel' }, context);
320
+ expect(result.success).toBe(false);
321
+ expect(result.error).toContain('Task ID is required');
322
+ });
323
+ });
324
+
325
+ // ── clear action ────────────────────────────────────────────────
326
+ describe('execute - clear', () => {
327
+ test('removes completed and cancelled tasks', async () => {
328
+ const { tool, agent, context } = createTestSetup();
329
+ await tool.execute({ action: 'create', title: 'T1', priority: 'medium' }, context);
330
+ await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
331
+ await tool.execute({ action: 'create', title: 'T3', priority: 'medium' }, context);
332
+ await tool.execute({ action: 'complete', taskId: agent.taskList.tasks[0].id }, context);
333
+ await tool.execute({ action: 'cancel', taskId: agent.taskList.tasks[1].id }, context);
334
+ const result = await tool.execute({ action: 'clear' }, context);
335
+ expect(result.result.removed).toBe(2);
336
+ expect(agent.taskList.tasks).toHaveLength(1);
337
+ });
338
+ });
339
+
340
+ // ── depend action ───────────────────────────────────────────────
341
+ describe('execute - depend', () => {
342
+ test('creates blocking dependency and sets blocked status', async () => {
343
+ const { tool, agent, context } = createTestSetup();
344
+ await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
345
+ await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
346
+ const [tA, tB] = agent.taskList.tasks;
347
+ const result = await tool.execute(
348
+ { action: 'depend', taskId: tB.id, dependsOn: tA.id, dependencyType: 'blocks' }, context
349
+ );
350
+ expect(result.success).toBe(true);
351
+ expect(result.result.dependency.taskId).toBe(tA.id);
352
+ expect(agent.taskList.tasks[1].status).toBe('blocked');
353
+ });
354
+
355
+ test('returns already-exists for duplicate dependency', async () => {
356
+ const { tool, agent, context } = createTestSetup();
357
+ await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
358
+ await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
359
+ const [tA, tB] = agent.taskList.tasks;
360
+ await tool.execute({ action: 'depend', taskId: tB.id, dependsOn: tA.id }, context);
361
+ const result = await tool.execute({ action: 'depend', taskId: tB.id, dependsOn: tA.id }, context);
362
+ expect(result.result.message).toContain('already exists');
363
+ });
364
+
365
+ test('errors when both params are missing', async () => {
366
+ const { tool, context } = createTestSetup();
367
+ const result = await tool.execute({ action: 'depend', taskId: 'x' }, context);
368
+ expect(result.success).toBe(false);
369
+ });
370
+
371
+ test('errors for invalid dependency type', async () => {
372
+ const { tool, agent, context } = createTestSetup();
373
+ await tool.execute({ action: 'create', title: 'A', priority: 'high' }, context);
374
+ await tool.execute({ action: 'create', title: 'B', priority: 'high' }, context);
375
+ const [a, b] = agent.taskList.tasks;
376
+ const result = await tool.execute(
377
+ { action: 'depend', taskId: b.id, dependsOn: a.id, dependencyType: 'invalid' }, context
378
+ );
379
+ expect(result.success).toBe(false);
380
+ expect(result.error).toContain('Invalid dependency type');
381
+ });
382
+ });
383
+
384
+ // ── relate action ───────────────────────────────────────────────
385
+ describe('execute - relate', () => {
386
+ test('creates a relates-type dependency', async () => {
387
+ const { tool, agent, context } = createTestSetup();
388
+ await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
389
+ await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
390
+ const [tA, tB] = agent.taskList.tasks;
391
+ const result = await tool.execute(
392
+ { action: 'relate', taskId: tB.id, dependsOn: tA.id }, context
393
+ );
394
+ expect(result.result.dependency.type).toBe('relates');
395
+ });
396
+ });
397
+
398
+ // ── subtask action ──────────────────────────────────────────────
399
+ describe('execute - subtask', () => {
400
+ test('creates subtask under parent', async () => {
401
+ const { tool, agent, context } = createTestSetup();
402
+ await tool.execute({ action: 'create', title: 'Parent', priority: 'high' }, context);
403
+ const parentId = agent.taskList.tasks[0].id;
404
+ const result = await tool.execute(
405
+ { action: 'subtask', parentTaskId: parentId, title: 'Sub 1', priority: 'medium' }, context
406
+ );
407
+ expect(result.success).toBe(true);
408
+ expect(result.result.subtask.isSubtask).toBe(true);
409
+ expect(result.result.subtask.parentTaskId).toBe(parentId);
410
+ expect(agent.taskList.tasks[0].subtasks).toContain(result.result.subtask.id);
411
+ });
412
+
413
+ test('errors when parent not found', async () => {
414
+ const { tool, context } = createTestSetup();
415
+ const result = await tool.execute(
416
+ { action: 'subtask', parentTaskId: 'no-such', title: 'Sub' }, context
417
+ );
418
+ expect(result.success).toBe(false);
419
+ expect(result.error).toContain('Parent task not found');
420
+ });
421
+
422
+ test('errors when parentTaskId or title missing', async () => {
423
+ const { tool, context } = createTestSetup();
424
+ const result = await tool.execute({ action: 'subtask', title: 'Sub' }, context);
425
+ expect(result.success).toBe(false);
426
+ expect(result.error).toContain('required');
427
+ });
428
+ });
429
+
430
+ // ── sync action ─────────────────────────────────────────────────
431
+ describe('execute - sync', () => {
432
+ test('syncs task list creating new tasks', async () => {
433
+ const { tool, context } = createTestSetup();
434
+ const result = await tool.execute({
435
+ action: 'sync',
436
+ tasks: [
437
+ { title: 'A', status: 'completed', priority: 'high' },
438
+ { title: 'B', status: 'in_progress', priority: 'medium' },
439
+ { title: 'C', status: 'pending', priority: 'low' }
440
+ ]
441
+ }, context);
442
+ expect(result.success).toBe(true);
443
+ expect(result.result.summary.total).toBe(3);
444
+ expect(result.result.summary.created).toBe(3);
445
+ });
446
+
447
+ test('updates existing tasks by matching title', async () => {
448
+ const { tool, context } = createTestSetup();
449
+ await tool.execute({ action: 'create', title: 'Existing', priority: 'low' }, context);
450
+ const result = await tool.execute({
451
+ action: 'sync',
452
+ tasks: [{ title: 'Existing', status: 'completed', priority: 'high' }]
453
+ }, context);
454
+ expect(result.result.summary.updated).toBe(1);
455
+ expect(result.result.summary.created).toBe(0);
456
+ });
457
+
458
+ test('parses JSON string tasks', async () => {
459
+ const { tool, context } = createTestSetup();
460
+ const result = await tool.execute({
461
+ action: 'sync',
462
+ tasks: JSON.stringify([{ title: 'JSON', status: 'pending', priority: 'medium' }])
463
+ }, context);
464
+ expect(result.success).toBe(true);
465
+ });
466
+
467
+ test('errors for empty tasks array', async () => {
468
+ const { tool, context } = createTestSetup();
469
+ const result = await tool.execute({ action: 'sync', tasks: [] }, context);
470
+ expect(result.success).toBe(false);
471
+ expect(result.error).toContain('empty');
472
+ });
473
+
474
+ test('errors for invalid status', async () => {
475
+ const { tool, context } = createTestSetup();
476
+ const result = await tool.execute({
477
+ action: 'sync',
478
+ tasks: [{ title: 'Bad', status: 'oops', priority: 'medium' }]
479
+ }, context);
480
+ expect(result.success).toBe(false);
481
+ expect(result.error).toContain('Invalid status');
482
+ });
483
+
484
+ test('enforces only one in_progress task', async () => {
485
+ const { tool, agent, context } = createTestSetup();
486
+ await tool.execute({
487
+ action: 'sync',
488
+ tasks: [
489
+ { title: 'A', status: 'in_progress', priority: 'high' },
490
+ { title: 'B', status: 'in_progress', priority: 'medium' }
491
+ ]
492
+ }, context);
493
+ const ipCount = agent.taskList.tasks.filter(t => t.status === 'in_progress').length;
494
+ expect(ipCount).toBe(1);
495
+ });
496
+
497
+ test('auto-sets first pending to in_progress when none active', async () => {
498
+ const { tool, agent, context } = createTestSetup();
499
+ await tool.execute({
500
+ action: 'sync',
501
+ tasks: [
502
+ { title: 'A', status: 'pending', priority: 'high' },
503
+ { title: 'B', status: 'pending', priority: 'medium' }
504
+ ]
505
+ }, context);
506
+ const ipTasks = agent.taskList.tasks.filter(t => t.status === 'in_progress');
507
+ expect(ipTasks).toHaveLength(1);
508
+ });
509
+ });
510
+
511
+ // ── template action ─────────────────────────────────────────────
512
+ describe('execute - template', () => {
513
+ test('lists available templates', async () => {
514
+ const { tool, context } = createTestSetup();
515
+ const result = await tool.execute({ action: 'template', mode: 'list' }, context);
516
+ expect(result.success).toBe(true);
517
+ expect(result.result.builtInTemplates.length).toBeGreaterThan(0);
518
+ });
519
+
520
+ test('applies a built-in template', async () => {
521
+ const { tool, agent, context } = createTestSetup();
522
+ const result = await tool.execute(
523
+ { action: 'template', mode: 'apply', templateId: 'bug-fix' }, context
524
+ );
525
+ expect(result.success).toBe(true);
526
+ expect(result.result.tasksCreated).toBeGreaterThan(0);
527
+ expect(agent.taskList.tasks.length).toBeGreaterThan(0);
528
+ });
529
+
530
+ test('errors for non-existent template', async () => {
531
+ const { tool, context } = createTestSetup();
532
+ const result = await tool.execute(
533
+ { action: 'template', mode: 'apply', templateId: 'nope' }, context
534
+ );
535
+ expect(result.success).toBe(false);
536
+ expect(result.error).toContain('Template not found');
537
+ });
538
+
539
+ test('creates a custom template', async () => {
540
+ const { tool, agent, context } = createTestSetup();
541
+ const result = await tool.execute({
542
+ action: 'template',
543
+ mode: 'create',
544
+ customTemplate: {
545
+ name: 'My Workflow',
546
+ description: 'Custom',
547
+ tasks: [{ title: 'Step 1' }, { title: 'Step 2' }]
548
+ }
549
+ }, context);
550
+ expect(result.success).toBe(true);
551
+ expect(agent.customTemplates).toHaveLength(1);
552
+ });
553
+
554
+ test('errors when custom template has no tasks', async () => {
555
+ const { tool, context } = createTestSetup();
556
+ const result = await tool.execute({
557
+ action: 'template',
558
+ mode: 'create',
559
+ customTemplate: { name: 'Empty', tasks: [] }
560
+ }, context);
561
+ expect(result.success).toBe(false);
562
+ expect(result.error).toContain('requires name and at least one task');
563
+ });
564
+
565
+ test('suggests templates based on patterns', async () => {
566
+ const { tool, agent, context } = createTestSetup();
567
+ agent.taskList.tasks.push({
568
+ id: 'bug-task', title: 'Fix login bug', status: 'pending',
569
+ priority: 'high', createdAt: new Date().toISOString()
570
+ });
571
+ const result = await tool.execute({ action: 'template', mode: 'suggest' }, context);
572
+ expect(result.success).toBe(true);
573
+ expect(result.result.suggestions.length).toBeGreaterThan(0);
574
+ });
575
+
576
+ test('errors for invalid template mode', async () => {
577
+ const { tool, context } = createTestSetup();
578
+ const result = await tool.execute({ action: 'template', mode: 'invalid' }, context);
579
+ expect(result.success).toBe(false);
580
+ expect(result.error).toContain('Invalid template mode');
581
+ });
582
+ });
583
+
584
+ // ── progress action ─────────────────────────────────────────────
585
+ describe('execute - progress', () => {
586
+ test('updates task progress with stage and note', async () => {
587
+ const { tool, agent, context } = createTestSetup();
588
+ await tool.execute({ action: 'create', title: 'Dev', priority: 'high' }, context);
589
+ const taskId = agent.taskList.tasks[0].id;
590
+ const result = await tool.execute({
591
+ action: 'progress', mode: 'update', taskId,
592
+ stage: 'in_development', note: 'Started coding'
593
+ }, context);
594
+ expect(result.success).toBe(true);
595
+ expect(result.result.task.progress.stage).toBe('in_development');
596
+ expect(result.result.task.progress.notes).toHaveLength(1);
597
+ });
598
+
599
+ test('setting percentage to 100 completes task', async () => {
600
+ const { tool, agent, context } = createTestSetup();
601
+ await tool.execute({ action: 'create', title: 'Pct', priority: 'high' }, context);
602
+ const taskId = agent.taskList.tasks[0].id;
603
+ await tool.execute({ action: 'progress', mode: 'update', taskId, percentage: 100 }, context);
604
+ expect(agent.taskList.tasks[0].status).toBe('completed');
605
+ expect(agent.taskList.tasks[0].progress.stage).toBe('completed');
606
+ });
607
+
608
+ test('gets progress overview', async () => {
609
+ const { tool, context } = createTestSetup();
610
+ await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
611
+ const result = await tool.execute({ action: 'progress', mode: 'overview' }, context);
612
+ expect(result.success).toBe(true);
613
+ expect(result.result.overview).toBeDefined();
614
+ });
615
+
616
+ test('calculates progress from subtasks', async () => {
617
+ const { tool, agent, context } = createTestSetup();
618
+ await tool.execute({ action: 'create', title: 'Parent', priority: 'high' }, context);
619
+ const parentId = agent.taskList.tasks[0].id;
620
+ await tool.execute({ action: 'subtask', parentTaskId: parentId, title: 'Sub1' }, context);
621
+ const result = await tool.execute({ action: 'progress', mode: 'calculate', taskId: parentId }, context);
622
+ expect(result.result.calculationMethod).toBe('subtasks');
623
+ });
624
+
625
+ test('errors for invalid stage', async () => {
626
+ const { tool, agent, context } = createTestSetup();
627
+ await tool.execute({ action: 'create', title: 'T', priority: 'high' }, context);
628
+ const taskId = agent.taskList.tasks[0].id;
629
+ const result = await tool.execute({ action: 'progress', mode: 'update', taskId, stage: 'nope' }, context);
630
+ expect(result.success).toBe(false);
631
+ expect(result.error).toContain('Invalid progress stage');
632
+ });
633
+
634
+ test('errors for invalid progress mode', async () => {
635
+ const { tool, context } = createTestSetup();
636
+ const result = await tool.execute({ action: 'progress', mode: 'invalid' }, context);
637
+ expect(result.success).toBe(false);
638
+ });
639
+ });
640
+
641
+ // ── prioritize action ───────────────────────────────────────────
642
+ describe('execute - prioritize', () => {
643
+ test('auto-prioritizes tasks', async () => {
644
+ const { tool, context } = createTestSetup();
645
+ await tool.execute({ action: 'create', title: 'T1', priority: 'low' }, context);
646
+ await tool.execute({ action: 'create', title: 'T2', priority: 'medium' }, context);
647
+ const result = await tool.execute({ action: 'prioritize', mode: 'auto' }, context);
648
+ expect(result.success).toBe(true);
649
+ });
650
+
651
+ test('analyzes specific task priority', async () => {
652
+ const { tool, agent, context } = createTestSetup();
653
+ await tool.execute({ action: 'create', title: 'Analyze', priority: 'medium' }, context);
654
+ const taskId = agent.taskList.tasks[0].id;
655
+ const result = await tool.execute({ action: 'prioritize', mode: 'analyze', taskId }, context);
656
+ expect(result.success).toBe(true);
657
+ expect(result.result.task.priorityScore).toBeDefined();
658
+ });
659
+
660
+ test('balance mode without scheduler returns message', async () => {
661
+ const { tool, context } = createTestSetup();
662
+ const result = await tool.execute({ action: 'prioritize', mode: 'balance' }, context);
663
+ expect(result.success).toBe(true);
664
+ expect(result.result.message).toContain('scheduler');
665
+ });
666
+
667
+ test('errors for invalid mode', async () => {
668
+ const { tool, context } = createTestSetup();
669
+ const result = await tool.execute({ action: 'prioritize', mode: 'xyz' }, context);
670
+ expect(result.success).toBe(false);
671
+ });
672
+ });
673
+
674
+ // ── analytics action ────────────────────────────────────────────
675
+ describe('execute - analytics', () => {
676
+ test('generates summary analytics', async () => {
677
+ const { tool, context } = createTestSetup();
678
+ await tool.execute({ action: 'create', title: 'T1', priority: 'high' }, context);
679
+ const result = await tool.execute({ action: 'analytics', mode: 'summary' }, context);
680
+ expect(result.success).toBe(true);
681
+ expect(result.result.generatedAt).toBeDefined();
682
+ });
683
+
684
+ test('errors for invalid analytics mode', async () => {
685
+ const { tool, context } = createTestSetup();
686
+ const result = await tool.execute({ action: 'analytics', mode: 'invalid' }, context);
687
+ expect(result.success).toBe(false);
688
+ expect(result.error).toContain('Invalid analytics mode');
689
+ });
690
+ });
691
+
692
+ // ── error handling ──────────────────────────────────────────────
693
+ describe('execute - error handling', () => {
694
+ test('fails when agentId is missing', async () => {
695
+ const { tool } = createTestSetup();
696
+ const result = await tool.execute({ action: 'list' }, { agentPool: {} });
697
+ expect(result.success).toBe(false);
698
+ expect(result.error).toContain('Agent ID is required');
699
+ });
700
+
701
+ test('fails when agent is not found', async () => {
702
+ const { tool } = createTestSetup();
703
+ const pool = { getAgent: jest.fn().mockResolvedValue(null) };
704
+ const result = await tool.execute({ action: 'list' }, { agentId: 'x', agentPool: pool });
705
+ expect(result.success).toBe(false);
706
+ expect(result.error).toContain('Agent not found');
707
+ });
708
+
709
+ test('fails for unsupported action', async () => {
710
+ const { tool, context } = createTestSetup();
711
+ const result = await tool.execute({ action: 'fly' }, context);
712
+ expect(result.success).toBe(false);
713
+ expect(result.error).toContain('Unsupported action');
714
+ });
715
+
716
+ test('initializes taskList on agent if missing', async () => {
717
+ const { tool, context, agentPool } = createTestSetup();
718
+ const bare = { id: 'agent-1', name: 'Bare' };
719
+ agentPool.getAgent.mockResolvedValue(bare);
720
+ const result = await tool.execute({ action: 'list' }, context);
721
+ expect(result.success).toBe(true);
722
+ expect(bare.taskList).toBeDefined();
723
+ });
724
+ });
725
+ });