code-graph-context 2.4.5 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +466 -620
- package/dist/mcp/constants.js +274 -0
- package/dist/mcp/handlers/swarm-worker.handler.js +251 -0
- package/dist/mcp/handlers/task-decomposition.handler.js +294 -0
- package/dist/mcp/tools/index.js +13 -1
- package/dist/mcp/tools/swarm-claim-task.tool.js +331 -0
- package/dist/mcp/tools/swarm-complete-task.tool.js +421 -0
- package/dist/mcp/tools/swarm-constants.js +113 -0
- package/dist/mcp/tools/swarm-get-tasks.tool.js +419 -0
- package/dist/mcp/tools/swarm-orchestrate.tool.js +389 -0
- package/dist/mcp/tools/swarm-post-task.tool.js +220 -0
- package/package.json +1 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm Complete Task Tool
|
|
3
|
+
* Mark a task as completed, failed, or needs_review with artifacts
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { Neo4jService } from '../../storage/neo4j/neo4j.service.js';
|
|
7
|
+
import { TOOL_NAMES, TOOL_METADATA } from '../constants.js';
|
|
8
|
+
import { createErrorResponse, createSuccessResponse, resolveProjectIdOrError, debugLog } from '../utils.js';
|
|
9
|
+
/**
|
|
10
|
+
* Query to complete a task with artifacts
|
|
11
|
+
*/
|
|
12
|
+
const COMPLETE_TASK_QUERY = `
|
|
13
|
+
MATCH (t:SwarmTask {id: $taskId, projectId: $projectId})
|
|
14
|
+
WHERE t.status = 'in_progress' AND t.claimedBy = $agentId
|
|
15
|
+
|
|
16
|
+
SET t.status = 'completed',
|
|
17
|
+
t.completedAt = timestamp(),
|
|
18
|
+
t.updatedAt = timestamp(),
|
|
19
|
+
t.summary = $summary,
|
|
20
|
+
t.artifacts = $artifacts,
|
|
21
|
+
t.filesChanged = $filesChanged,
|
|
22
|
+
t.linesAdded = $linesAdded,
|
|
23
|
+
t.linesRemoved = $linesRemoved
|
|
24
|
+
|
|
25
|
+
// Check for tasks that were blocked by this one
|
|
26
|
+
WITH t
|
|
27
|
+
OPTIONAL MATCH (waiting:SwarmTask)-[:DEPENDS_ON]->(t)
|
|
28
|
+
WHERE waiting.status = 'blocked'
|
|
29
|
+
|
|
30
|
+
// Check if waiting tasks now have all dependencies completed
|
|
31
|
+
WITH t, collect(waiting) as waitingTasks
|
|
32
|
+
UNWIND (CASE WHEN size(waitingTasks) = 0 THEN [null] ELSE waitingTasks END) as waiting
|
|
33
|
+
OPTIONAL MATCH (waiting)-[:DEPENDS_ON]->(otherDep:SwarmTask)
|
|
34
|
+
WHERE otherDep.status <> 'completed' AND otherDep.id <> t.id
|
|
35
|
+
WITH t, waiting, count(otherDep) as remainingDeps
|
|
36
|
+
WHERE waiting IS NOT NULL AND remainingDeps = 0
|
|
37
|
+
|
|
38
|
+
// Unblock tasks that now have all dependencies met
|
|
39
|
+
SET waiting.status = 'available',
|
|
40
|
+
waiting.updatedAt = timestamp()
|
|
41
|
+
|
|
42
|
+
WITH t, collect(waiting.id) as unblockedTaskIds
|
|
43
|
+
|
|
44
|
+
RETURN t.id as id,
|
|
45
|
+
t.title as title,
|
|
46
|
+
t.status as status,
|
|
47
|
+
t.completedAt as completedAt,
|
|
48
|
+
t.summary as summary,
|
|
49
|
+
t.claimedBy as claimedBy,
|
|
50
|
+
unblockedTaskIds
|
|
51
|
+
`;
|
|
52
|
+
/**
|
|
53
|
+
* Query to mark task as failed
|
|
54
|
+
*/
|
|
55
|
+
const FAIL_TASK_QUERY = `
|
|
56
|
+
MATCH (t:SwarmTask {id: $taskId, projectId: $projectId})
|
|
57
|
+
WHERE t.status IN ['in_progress', 'claimed'] AND t.claimedBy = $agentId
|
|
58
|
+
|
|
59
|
+
SET t.status = 'failed',
|
|
60
|
+
t.failedAt = timestamp(),
|
|
61
|
+
t.updatedAt = timestamp(),
|
|
62
|
+
t.failureReason = $reason,
|
|
63
|
+
t.errorDetails = $errorDetails,
|
|
64
|
+
t.retryable = $retryable
|
|
65
|
+
|
|
66
|
+
RETURN t.id as id,
|
|
67
|
+
t.title as title,
|
|
68
|
+
t.status as status,
|
|
69
|
+
t.failedAt as failedAt,
|
|
70
|
+
t.failureReason as failureReason,
|
|
71
|
+
t.retryable as retryable
|
|
72
|
+
`;
|
|
73
|
+
/**
|
|
74
|
+
* Query to mark task as needs_review
|
|
75
|
+
*/
|
|
76
|
+
const REVIEW_TASK_QUERY = `
|
|
77
|
+
MATCH (t:SwarmTask {id: $taskId, projectId: $projectId})
|
|
78
|
+
WHERE t.status = 'in_progress' AND t.claimedBy = $agentId
|
|
79
|
+
|
|
80
|
+
SET t.status = 'needs_review',
|
|
81
|
+
t.reviewRequestedAt = timestamp(),
|
|
82
|
+
t.updatedAt = timestamp(),
|
|
83
|
+
t.summary = $summary,
|
|
84
|
+
t.artifacts = $artifacts,
|
|
85
|
+
t.filesChanged = $filesChanged,
|
|
86
|
+
t.reviewNotes = $reviewNotes
|
|
87
|
+
|
|
88
|
+
RETURN t.id as id,
|
|
89
|
+
t.title as title,
|
|
90
|
+
t.status as status,
|
|
91
|
+
t.reviewRequestedAt as reviewRequestedAt,
|
|
92
|
+
t.summary as summary,
|
|
93
|
+
t.claimedBy as claimedBy
|
|
94
|
+
`;
|
|
95
|
+
/**
|
|
96
|
+
* Query to approve a reviewed task (transition to completed)
|
|
97
|
+
*/
|
|
98
|
+
const APPROVE_TASK_QUERY = `
|
|
99
|
+
MATCH (t:SwarmTask {id: $taskId, projectId: $projectId})
|
|
100
|
+
WHERE t.status = 'needs_review'
|
|
101
|
+
|
|
102
|
+
SET t.status = 'completed',
|
|
103
|
+
t.completedAt = timestamp(),
|
|
104
|
+
t.updatedAt = timestamp(),
|
|
105
|
+
t.approvedBy = $reviewerId,
|
|
106
|
+
t.approvalNotes = $notes
|
|
107
|
+
|
|
108
|
+
// Check for tasks that were blocked by this one
|
|
109
|
+
WITH t
|
|
110
|
+
OPTIONAL MATCH (waiting:SwarmTask)-[:DEPENDS_ON]->(t)
|
|
111
|
+
WHERE waiting.status = 'blocked'
|
|
112
|
+
|
|
113
|
+
// Check if waiting tasks now have all dependencies completed
|
|
114
|
+
WITH t, collect(waiting) as waitingTasks
|
|
115
|
+
UNWIND (CASE WHEN size(waitingTasks) = 0 THEN [null] ELSE waitingTasks END) as waiting
|
|
116
|
+
OPTIONAL MATCH (waiting)-[:DEPENDS_ON]->(otherDep:SwarmTask)
|
|
117
|
+
WHERE otherDep.status <> 'completed' AND otherDep.id <> t.id
|
|
118
|
+
WITH t, waiting, count(otherDep) as remainingDeps
|
|
119
|
+
WHERE waiting IS NOT NULL AND remainingDeps = 0
|
|
120
|
+
|
|
121
|
+
SET waiting.status = 'available',
|
|
122
|
+
waiting.updatedAt = timestamp()
|
|
123
|
+
|
|
124
|
+
WITH t, collect(waiting.id) as unblockedTaskIds
|
|
125
|
+
|
|
126
|
+
RETURN t.id as id,
|
|
127
|
+
t.title as title,
|
|
128
|
+
t.status as status,
|
|
129
|
+
t.completedAt as completedAt,
|
|
130
|
+
t.approvedBy as approvedBy,
|
|
131
|
+
unblockedTaskIds
|
|
132
|
+
`;
|
|
133
|
+
/**
|
|
134
|
+
* Query to reject a reviewed task (back to in_progress or failed)
|
|
135
|
+
*/
|
|
136
|
+
const REJECT_TASK_QUERY = `
|
|
137
|
+
MATCH (t:SwarmTask {id: $taskId, projectId: $projectId})
|
|
138
|
+
WHERE t.status = 'needs_review'
|
|
139
|
+
|
|
140
|
+
SET t.status = CASE WHEN $markAsFailed THEN 'failed' ELSE 'in_progress' END,
|
|
141
|
+
t.updatedAt = timestamp(),
|
|
142
|
+
t.rejectedBy = $reviewerId,
|
|
143
|
+
t.rejectionNotes = $notes,
|
|
144
|
+
t.rejectedAt = timestamp()
|
|
145
|
+
|
|
146
|
+
RETURN t.id as id,
|
|
147
|
+
t.title as title,
|
|
148
|
+
t.status as status,
|
|
149
|
+
t.claimedBy as claimedBy,
|
|
150
|
+
t.rejectionNotes as rejectionNotes
|
|
151
|
+
`;
|
|
152
|
+
/**
|
|
153
|
+
* Query to retry a failed task
|
|
154
|
+
*/
|
|
155
|
+
const RETRY_TASK_QUERY = `
|
|
156
|
+
MATCH (t:SwarmTask {id: $taskId, projectId: $projectId})
|
|
157
|
+
WHERE t.status = 'failed' AND t.retryable = true
|
|
158
|
+
|
|
159
|
+
SET t.status = 'available',
|
|
160
|
+
t.claimedBy = null,
|
|
161
|
+
t.claimedAt = null,
|
|
162
|
+
t.startedAt = null,
|
|
163
|
+
t.failedAt = null,
|
|
164
|
+
t.failureReason = null,
|
|
165
|
+
t.errorDetails = null,
|
|
166
|
+
t.updatedAt = timestamp(),
|
|
167
|
+
t.retryCount = COALESCE(t.retryCount, 0) + 1
|
|
168
|
+
|
|
169
|
+
RETURN t.id as id,
|
|
170
|
+
t.title as title,
|
|
171
|
+
t.status as status,
|
|
172
|
+
t.retryCount as retryCount
|
|
173
|
+
`;
|
|
174
|
+
export const createSwarmCompleteTaskTool = (server) => {
|
|
175
|
+
server.registerTool(TOOL_NAMES.swarmCompleteTask, {
|
|
176
|
+
title: TOOL_METADATA[TOOL_NAMES.swarmCompleteTask].title,
|
|
177
|
+
description: TOOL_METADATA[TOOL_NAMES.swarmCompleteTask].description,
|
|
178
|
+
inputSchema: {
|
|
179
|
+
projectId: z.string().describe('Project ID, name, or path'),
|
|
180
|
+
taskId: z.string().describe('Task ID to complete'),
|
|
181
|
+
agentId: z.string().describe('Your agent ID (must match the agent who claimed the task)'),
|
|
182
|
+
action: z
|
|
183
|
+
.enum(['complete', 'fail', 'request_review', 'approve', 'reject', 'retry'])
|
|
184
|
+
.describe('Action to take on the task'),
|
|
185
|
+
summary: z
|
|
186
|
+
.string()
|
|
187
|
+
.optional()
|
|
188
|
+
.describe('Summary of what was done (required for complete/request_review)'),
|
|
189
|
+
artifacts: z
|
|
190
|
+
.record(z.unknown())
|
|
191
|
+
.optional()
|
|
192
|
+
.describe('Artifacts produced: { files: [], commits: [], pullRequests: [], notes: string }'),
|
|
193
|
+
filesChanged: z
|
|
194
|
+
.array(z.string())
|
|
195
|
+
.optional()
|
|
196
|
+
.describe('List of files that were modified'),
|
|
197
|
+
linesAdded: z.number().int().optional().describe('Number of lines added'),
|
|
198
|
+
linesRemoved: z.number().int().optional().describe('Number of lines removed'),
|
|
199
|
+
reason: z
|
|
200
|
+
.string()
|
|
201
|
+
.optional()
|
|
202
|
+
.describe('Reason for failure (required if action=fail)'),
|
|
203
|
+
errorDetails: z
|
|
204
|
+
.string()
|
|
205
|
+
.optional()
|
|
206
|
+
.describe('Technical error details for debugging'),
|
|
207
|
+
retryable: z
|
|
208
|
+
.boolean()
|
|
209
|
+
.optional()
|
|
210
|
+
.default(true)
|
|
211
|
+
.describe('Whether the task can be retried after failure'),
|
|
212
|
+
reviewNotes: z
|
|
213
|
+
.string()
|
|
214
|
+
.optional()
|
|
215
|
+
.describe('Notes for the reviewer (for request_review)'),
|
|
216
|
+
reviewerId: z
|
|
217
|
+
.string()
|
|
218
|
+
.optional()
|
|
219
|
+
.describe('ID of the reviewer (required for approve/reject)'),
|
|
220
|
+
notes: z
|
|
221
|
+
.string()
|
|
222
|
+
.optional()
|
|
223
|
+
.describe('Approval/rejection notes'),
|
|
224
|
+
markAsFailed: z
|
|
225
|
+
.boolean()
|
|
226
|
+
.optional()
|
|
227
|
+
.default(false)
|
|
228
|
+
.describe('If rejecting, mark as failed instead of returning to in_progress'),
|
|
229
|
+
},
|
|
230
|
+
}, async ({ projectId, taskId, agentId, action, summary, artifacts, filesChanged, linesAdded, linesRemoved, reason, errorDetails, retryable = true, reviewNotes, reviewerId, notes, markAsFailed = false, }) => {
|
|
231
|
+
const neo4jService = new Neo4jService();
|
|
232
|
+
// Resolve project ID
|
|
233
|
+
const projectResult = await resolveProjectIdOrError(projectId, neo4jService);
|
|
234
|
+
if (!projectResult.success) {
|
|
235
|
+
await neo4jService.close();
|
|
236
|
+
return projectResult.error;
|
|
237
|
+
}
|
|
238
|
+
const resolvedProjectId = projectResult.projectId;
|
|
239
|
+
try {
|
|
240
|
+
await debugLog('Swarm complete task', {
|
|
241
|
+
action,
|
|
242
|
+
taskId,
|
|
243
|
+
projectId: resolvedProjectId,
|
|
244
|
+
agentId,
|
|
245
|
+
});
|
|
246
|
+
let result;
|
|
247
|
+
let responseData = { success: true, action };
|
|
248
|
+
switch (action) {
|
|
249
|
+
case 'complete':
|
|
250
|
+
if (!summary) {
|
|
251
|
+
return createErrorResponse('summary is required for complete action');
|
|
252
|
+
}
|
|
253
|
+
result = await neo4jService.run(COMPLETE_TASK_QUERY, {
|
|
254
|
+
taskId,
|
|
255
|
+
projectId: resolvedProjectId,
|
|
256
|
+
agentId,
|
|
257
|
+
summary,
|
|
258
|
+
artifacts: artifacts ? JSON.stringify(artifacts) : null,
|
|
259
|
+
filesChanged: filesChanged || [],
|
|
260
|
+
linesAdded: linesAdded || 0,
|
|
261
|
+
linesRemoved: linesRemoved || 0,
|
|
262
|
+
});
|
|
263
|
+
if (result.length === 0) {
|
|
264
|
+
return createErrorResponse(`Cannot complete task ${taskId}. It may not exist, not be in_progress, or you don't own it.`);
|
|
265
|
+
}
|
|
266
|
+
responseData.task = {
|
|
267
|
+
id: result[0].id,
|
|
268
|
+
title: result[0].title,
|
|
269
|
+
status: result[0].status,
|
|
270
|
+
completedAt: typeof result[0].completedAt === 'object'
|
|
271
|
+
? result[0].completedAt.toNumber()
|
|
272
|
+
: result[0].completedAt,
|
|
273
|
+
summary: result[0].summary,
|
|
274
|
+
claimedBy: result[0].claimedBy,
|
|
275
|
+
};
|
|
276
|
+
responseData.unblockedTasks = result[0].unblockedTaskIds || [];
|
|
277
|
+
responseData.message = responseData.unblockedTasks.length > 0
|
|
278
|
+
? `Task completed. ${responseData.unblockedTasks.length} dependent task(s) are now available.`
|
|
279
|
+
: 'Task completed successfully.';
|
|
280
|
+
break;
|
|
281
|
+
case 'fail':
|
|
282
|
+
if (!reason) {
|
|
283
|
+
return createErrorResponse('reason is required for fail action');
|
|
284
|
+
}
|
|
285
|
+
result = await neo4jService.run(FAIL_TASK_QUERY, {
|
|
286
|
+
taskId,
|
|
287
|
+
projectId: resolvedProjectId,
|
|
288
|
+
agentId,
|
|
289
|
+
reason,
|
|
290
|
+
errorDetails: errorDetails || null,
|
|
291
|
+
retryable,
|
|
292
|
+
});
|
|
293
|
+
if (result.length === 0) {
|
|
294
|
+
return createErrorResponse(`Cannot fail task ${taskId}. It may not exist, not be in_progress/claimed, or you don't own it.`);
|
|
295
|
+
}
|
|
296
|
+
responseData.task = {
|
|
297
|
+
id: result[0].id,
|
|
298
|
+
title: result[0].title,
|
|
299
|
+
status: result[0].status,
|
|
300
|
+
failedAt: typeof result[0].failedAt === 'object'
|
|
301
|
+
? result[0].failedAt.toNumber()
|
|
302
|
+
: result[0].failedAt,
|
|
303
|
+
failureReason: result[0].failureReason,
|
|
304
|
+
retryable: result[0].retryable,
|
|
305
|
+
};
|
|
306
|
+
responseData.message = retryable
|
|
307
|
+
? 'Task marked as failed. Use action="retry" to make it available again.'
|
|
308
|
+
: 'Task marked as failed (not retryable).';
|
|
309
|
+
break;
|
|
310
|
+
case 'request_review':
|
|
311
|
+
if (!summary) {
|
|
312
|
+
return createErrorResponse('summary is required for request_review action');
|
|
313
|
+
}
|
|
314
|
+
result = await neo4jService.run(REVIEW_TASK_QUERY, {
|
|
315
|
+
taskId,
|
|
316
|
+
projectId: resolvedProjectId,
|
|
317
|
+
agentId,
|
|
318
|
+
summary,
|
|
319
|
+
artifacts: artifacts ? JSON.stringify(artifacts) : null,
|
|
320
|
+
filesChanged: filesChanged || [],
|
|
321
|
+
reviewNotes: reviewNotes || null,
|
|
322
|
+
});
|
|
323
|
+
if (result.length === 0) {
|
|
324
|
+
return createErrorResponse(`Cannot request review for task ${taskId}. It may not exist, not be in_progress, or you don't own it.`);
|
|
325
|
+
}
|
|
326
|
+
responseData.task = {
|
|
327
|
+
id: result[0].id,
|
|
328
|
+
title: result[0].title,
|
|
329
|
+
status: result[0].status,
|
|
330
|
+
reviewRequestedAt: typeof result[0].reviewRequestedAt === 'object'
|
|
331
|
+
? result[0].reviewRequestedAt.toNumber()
|
|
332
|
+
: result[0].reviewRequestedAt,
|
|
333
|
+
summary: result[0].summary,
|
|
334
|
+
claimedBy: result[0].claimedBy,
|
|
335
|
+
};
|
|
336
|
+
responseData.message = 'Task submitted for review.';
|
|
337
|
+
break;
|
|
338
|
+
case 'approve':
|
|
339
|
+
if (!reviewerId) {
|
|
340
|
+
return createErrorResponse('reviewerId is required for approve action');
|
|
341
|
+
}
|
|
342
|
+
result = await neo4jService.run(APPROVE_TASK_QUERY, {
|
|
343
|
+
taskId,
|
|
344
|
+
projectId: resolvedProjectId,
|
|
345
|
+
reviewerId,
|
|
346
|
+
notes: notes || null,
|
|
347
|
+
});
|
|
348
|
+
if (result.length === 0) {
|
|
349
|
+
return createErrorResponse(`Cannot approve task ${taskId}. It may not exist or not be in needs_review status.`);
|
|
350
|
+
}
|
|
351
|
+
responseData.task = {
|
|
352
|
+
id: result[0].id,
|
|
353
|
+
title: result[0].title,
|
|
354
|
+
status: result[0].status,
|
|
355
|
+
completedAt: typeof result[0].completedAt === 'object'
|
|
356
|
+
? result[0].completedAt.toNumber()
|
|
357
|
+
: result[0].completedAt,
|
|
358
|
+
approvedBy: result[0].approvedBy,
|
|
359
|
+
};
|
|
360
|
+
responseData.unblockedTasks = result[0].unblockedTaskIds || [];
|
|
361
|
+
responseData.message = responseData.unblockedTasks.length > 0
|
|
362
|
+
? `Task approved. ${responseData.unblockedTasks.length} dependent task(s) are now available.`
|
|
363
|
+
: 'Task approved and completed.';
|
|
364
|
+
break;
|
|
365
|
+
case 'reject':
|
|
366
|
+
if (!reviewerId) {
|
|
367
|
+
return createErrorResponse('reviewerId is required for reject action');
|
|
368
|
+
}
|
|
369
|
+
result = await neo4jService.run(REJECT_TASK_QUERY, {
|
|
370
|
+
taskId,
|
|
371
|
+
projectId: resolvedProjectId,
|
|
372
|
+
reviewerId,
|
|
373
|
+
notes: notes || 'No notes provided',
|
|
374
|
+
markAsFailed,
|
|
375
|
+
});
|
|
376
|
+
if (result.length === 0) {
|
|
377
|
+
return createErrorResponse(`Cannot reject task ${taskId}. It may not exist or not be in needs_review status.`);
|
|
378
|
+
}
|
|
379
|
+
responseData.task = {
|
|
380
|
+
id: result[0].id,
|
|
381
|
+
title: result[0].title,
|
|
382
|
+
status: result[0].status,
|
|
383
|
+
claimedBy: result[0].claimedBy,
|
|
384
|
+
rejectionNotes: result[0].rejectionNotes,
|
|
385
|
+
};
|
|
386
|
+
responseData.message = markAsFailed
|
|
387
|
+
? 'Task rejected and marked as failed.'
|
|
388
|
+
: 'Task rejected and returned to in_progress for the original agent to fix.';
|
|
389
|
+
break;
|
|
390
|
+
case 'retry':
|
|
391
|
+
result = await neo4jService.run(RETRY_TASK_QUERY, {
|
|
392
|
+
taskId,
|
|
393
|
+
projectId: resolvedProjectId,
|
|
394
|
+
});
|
|
395
|
+
if (result.length === 0) {
|
|
396
|
+
return createErrorResponse(`Cannot retry task ${taskId}. It may not exist, not be failed, or not be retryable.`);
|
|
397
|
+
}
|
|
398
|
+
responseData.task = {
|
|
399
|
+
id: result[0].id,
|
|
400
|
+
title: result[0].title,
|
|
401
|
+
status: result[0].status,
|
|
402
|
+
retryCount: typeof result[0].retryCount === 'object'
|
|
403
|
+
? result[0].retryCount.toNumber()
|
|
404
|
+
: result[0].retryCount,
|
|
405
|
+
};
|
|
406
|
+
responseData.message = `Task is now available for retry (attempt #${responseData.task.retryCount + 1}).`;
|
|
407
|
+
break;
|
|
408
|
+
default:
|
|
409
|
+
return createErrorResponse(`Unknown action: ${action}`);
|
|
410
|
+
}
|
|
411
|
+
return createSuccessResponse(JSON.stringify(responseData));
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
await debugLog('Swarm complete task error', { error: String(error) });
|
|
415
|
+
return createErrorResponse(error instanceof Error ? error : String(error));
|
|
416
|
+
}
|
|
417
|
+
finally {
|
|
418
|
+
await neo4jService.close();
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
};
|
|
@@ -15,6 +15,50 @@ export const PHEROMONE_CONFIG = {
|
|
|
15
15
|
proposal: { halfLife: 60 * 60 * 1000, description: 'Awaiting approval' },
|
|
16
16
|
needs_review: { halfLife: 30 * 60 * 1000, description: 'Review requested' },
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Task status values for the blackboard task queue
|
|
20
|
+
*/
|
|
21
|
+
export const TASK_STATUSES = [
|
|
22
|
+
'available', // Ready to be claimed by an agent
|
|
23
|
+
'claimed', // An agent has claimed but not started
|
|
24
|
+
'in_progress', // Agent is actively working
|
|
25
|
+
'blocked', // Task is blocked by dependencies or issues
|
|
26
|
+
'needs_review', // Work done, awaiting review
|
|
27
|
+
'completed', // Successfully finished
|
|
28
|
+
'failed', // Task failed
|
|
29
|
+
'cancelled', // Task was cancelled
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Task priority levels (higher = more urgent)
|
|
33
|
+
*/
|
|
34
|
+
export const TASK_PRIORITIES = {
|
|
35
|
+
critical: 100, // Must be done immediately
|
|
36
|
+
high: 75, // Important, do soon
|
|
37
|
+
normal: 50, // Standard priority
|
|
38
|
+
low: 25, // Can wait
|
|
39
|
+
backlog: 0, // Do when nothing else is available
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Task types for categorization
|
|
43
|
+
*/
|
|
44
|
+
export const TASK_TYPES = [
|
|
45
|
+
'implement', // Write new code
|
|
46
|
+
'refactor', // Improve existing code
|
|
47
|
+
'fix', // Bug fix
|
|
48
|
+
'test', // Write/fix tests
|
|
49
|
+
'review', // Code review
|
|
50
|
+
'document', // Documentation
|
|
51
|
+
'investigate', // Research/explore
|
|
52
|
+
'plan', // Planning/design
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* Generate a unique task ID
|
|
56
|
+
*/
|
|
57
|
+
export const generateTaskId = () => {
|
|
58
|
+
const timestamp = Date.now().toString(36);
|
|
59
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
60
|
+
return `task_${timestamp}_${random}`;
|
|
61
|
+
};
|
|
18
62
|
export const PHEROMONE_TYPES = Object.keys(PHEROMONE_CONFIG);
|
|
19
63
|
/**
|
|
20
64
|
* Get half-life for a pheromone type.
|
|
@@ -33,3 +77,72 @@ export const WORKFLOW_STATES = ['exploring', 'claiming', 'modifying', 'completed
|
|
|
33
77
|
* Flags can coexist with workflow states.
|
|
34
78
|
*/
|
|
35
79
|
export const FLAG_TYPES = ['warning', 'proposal', 'needs_review'];
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// ORCHESTRATOR CONSTANTS
|
|
82
|
+
// ============================================================================
|
|
83
|
+
/**
|
|
84
|
+
* Generate a unique swarm ID for orchestrator runs
|
|
85
|
+
*/
|
|
86
|
+
export const generateSwarmId = () => {
|
|
87
|
+
const timestamp = Date.now().toString(36);
|
|
88
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
89
|
+
return `swarm_${timestamp}_${random}`;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Generate a unique agent ID for worker agents
|
|
93
|
+
*/
|
|
94
|
+
export const generateAgentId = (swarmId, index) => {
|
|
95
|
+
return `${swarmId}_agent_${index}`;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Orchestrator configuration defaults
|
|
99
|
+
*/
|
|
100
|
+
export const ORCHESTRATOR_CONFIG = {
|
|
101
|
+
/** Default maximum number of concurrent worker agents */
|
|
102
|
+
defaultMaxAgents: 3,
|
|
103
|
+
/** Maximum allowed agents (hard limit) */
|
|
104
|
+
maxAgentsLimit: 10,
|
|
105
|
+
/** Polling interval for monitoring progress (ms) */
|
|
106
|
+
monitorIntervalMs: 1000,
|
|
107
|
+
/** Timeout for waiting on worker agents (ms) - 30 minutes */
|
|
108
|
+
workerTimeoutMs: 30 * 60 * 1000,
|
|
109
|
+
/** Delay between spawning agents (ms) */
|
|
110
|
+
spawnDelayMs: 500,
|
|
111
|
+
/** Minimum nodes to consider for parallelization */
|
|
112
|
+
minNodesForParallel: 3,
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Task inference patterns for decomposing natural language tasks
|
|
116
|
+
*/
|
|
117
|
+
export const TASK_INFERENCE_PATTERNS = {
|
|
118
|
+
rename: {
|
|
119
|
+
keywords: ['rename', 'change name', 'refactor name'],
|
|
120
|
+
taskType: 'refactor',
|
|
121
|
+
description: (oldName, newName) => `Rename "${oldName}" to "${newName}" and update all references`,
|
|
122
|
+
},
|
|
123
|
+
document: {
|
|
124
|
+
keywords: ['jsdoc', 'document', 'add comments', 'add documentation'],
|
|
125
|
+
taskType: 'document',
|
|
126
|
+
description: (target) => `Add documentation to ${target}`,
|
|
127
|
+
},
|
|
128
|
+
migrate: {
|
|
129
|
+
keywords: ['migrate', 'convert', 'upgrade', 'modernize'],
|
|
130
|
+
taskType: 'refactor',
|
|
131
|
+
description: (from, to) => `Migrate from ${from} to ${to}`,
|
|
132
|
+
},
|
|
133
|
+
deprecate: {
|
|
134
|
+
keywords: ['deprecate', 'deprecation warning', 'mark deprecated'],
|
|
135
|
+
taskType: 'refactor',
|
|
136
|
+
description: (target) => `Add deprecation warning to ${target}`,
|
|
137
|
+
},
|
|
138
|
+
fix: {
|
|
139
|
+
keywords: ['fix', 'repair', 'correct', 'resolve'],
|
|
140
|
+
taskType: 'fix',
|
|
141
|
+
description: (issue) => `Fix ${issue}`,
|
|
142
|
+
},
|
|
143
|
+
test: {
|
|
144
|
+
keywords: ['test', 'add tests', 'write tests', 'unit test'],
|
|
145
|
+
taskType: 'test',
|
|
146
|
+
description: (target) => `Write tests for ${target}`,
|
|
147
|
+
},
|
|
148
|
+
};
|