code-graph-context 2.5.2 → 2.5.4
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.
|
@@ -4,7 +4,7 @@ import { getTimeoutConfig } from '../config/timeouts.js';
|
|
|
4
4
|
export class NaturalLanguageToCypherService {
|
|
5
5
|
assistantId;
|
|
6
6
|
openai;
|
|
7
|
-
MODEL = 'gpt-4o
|
|
7
|
+
MODEL = 'gpt-4o'; // GPT-4o for better Cypher generation accuracy
|
|
8
8
|
schemaPath = null;
|
|
9
9
|
cachedSemanticTypes = null;
|
|
10
10
|
messageInstructions = `
|
|
@@ -66,9 +66,9 @@ const CLAIM_TASK_BY_ID_QUERY = `
|
|
|
66
66
|
* Uses APOC locking to prevent race conditions between parallel workers
|
|
67
67
|
*/
|
|
68
68
|
const CLAIM_NEXT_TASK_QUERY = `
|
|
69
|
-
// Find available tasks
|
|
69
|
+
// Find available or blocked tasks (blocked tasks may have deps completed now)
|
|
70
70
|
MATCH (t:SwarmTask {projectId: $projectId, swarmId: $swarmId})
|
|
71
|
-
WHERE t.status
|
|
71
|
+
WHERE t.status IN ['available', 'blocked']
|
|
72
72
|
AND ($types IS NULL OR size($types) = 0 OR t.type IN $types)
|
|
73
73
|
AND ($minPriority IS NULL OR t.priorityScore >= $minPriority)
|
|
74
74
|
|
|
@@ -86,7 +86,7 @@ const CLAIM_NEXT_TASK_QUERY = `
|
|
|
86
86
|
CALL apoc.lock.nodes([t])
|
|
87
87
|
|
|
88
88
|
// Double-check status after acquiring lock (another worker may have claimed it)
|
|
89
|
-
WITH t WHERE t.status
|
|
89
|
+
WITH t WHERE t.status IN ['available', 'blocked']
|
|
90
90
|
|
|
91
91
|
// Atomic claim
|
|
92
92
|
SET t.status = 'claimed',
|
|
@@ -9,7 +9,7 @@ import { createErrorResponse, createSuccessResponse, resolveProjectIdOrError, de
|
|
|
9
9
|
/**
|
|
10
10
|
* Neo4j query to delete pheromones by swarm ID
|
|
11
11
|
*/
|
|
12
|
-
const
|
|
12
|
+
const CLEANUP_PHEROMONES_BY_SWARM_QUERY = `
|
|
13
13
|
MATCH (p:Pheromone)
|
|
14
14
|
WHERE p.projectId = $projectId
|
|
15
15
|
AND p.swarmId = $swarmId
|
|
@@ -18,6 +18,17 @@ const CLEANUP_BY_SWARM_QUERY = `
|
|
|
18
18
|
DETACH DELETE p
|
|
19
19
|
RETURN count(p) as deleted, collect(DISTINCT agentId) as agents, collect(DISTINCT type) as types
|
|
20
20
|
`;
|
|
21
|
+
/**
|
|
22
|
+
* Neo4j query to delete SwarmTask nodes by swarm ID
|
|
23
|
+
*/
|
|
24
|
+
const CLEANUP_TASKS_BY_SWARM_QUERY = `
|
|
25
|
+
MATCH (t:SwarmTask)
|
|
26
|
+
WHERE t.projectId = $projectId
|
|
27
|
+
AND t.swarmId = $swarmId
|
|
28
|
+
WITH t, t.status as status
|
|
29
|
+
DETACH DELETE t
|
|
30
|
+
RETURN count(t) as deleted, collect(DISTINCT status) as statuses
|
|
31
|
+
`;
|
|
21
32
|
/**
|
|
22
33
|
* Neo4j query to delete pheromones by agent ID
|
|
23
34
|
*/
|
|
@@ -44,11 +55,16 @@ const CLEANUP_ALL_QUERY = `
|
|
|
44
55
|
/**
|
|
45
56
|
* Count queries for dry run
|
|
46
57
|
*/
|
|
47
|
-
const
|
|
58
|
+
const COUNT_PHEROMONES_BY_SWARM_QUERY = `
|
|
48
59
|
MATCH (p:Pheromone)
|
|
49
60
|
WHERE p.projectId = $projectId AND p.swarmId = $swarmId AND NOT p.type IN $keepTypes
|
|
50
61
|
RETURN count(p) as count, collect(DISTINCT p.agentId) as agents, collect(DISTINCT p.type) as types
|
|
51
62
|
`;
|
|
63
|
+
const COUNT_TASKS_BY_SWARM_QUERY = `
|
|
64
|
+
MATCH (t:SwarmTask)
|
|
65
|
+
WHERE t.projectId = $projectId AND t.swarmId = $swarmId
|
|
66
|
+
RETURN count(t) as count, collect(DISTINCT t.status) as statuses
|
|
67
|
+
`;
|
|
52
68
|
const COUNT_BY_AGENT_QUERY = `
|
|
53
69
|
MATCH (p:Pheromone)
|
|
54
70
|
WHERE p.projectId = $projectId AND p.agentId = $agentId AND NOT p.type IN $keepTypes
|
|
@@ -65,9 +81,14 @@ export const createSwarmCleanupTool = (server) => {
|
|
|
65
81
|
description: TOOL_METADATA[TOOL_NAMES.swarmCleanup].description,
|
|
66
82
|
inputSchema: {
|
|
67
83
|
projectId: z.string().describe('Project ID, name, or path'),
|
|
68
|
-
swarmId: z.string().optional().describe('Delete all pheromones from this swarm'),
|
|
84
|
+
swarmId: z.string().optional().describe('Delete all pheromones and tasks from this swarm'),
|
|
69
85
|
agentId: z.string().optional().describe('Delete all pheromones from this agent'),
|
|
70
86
|
all: z.boolean().optional().default(false).describe('Delete ALL pheromones in project (use with caution)'),
|
|
87
|
+
includeTasks: z
|
|
88
|
+
.boolean()
|
|
89
|
+
.optional()
|
|
90
|
+
.default(true)
|
|
91
|
+
.describe('Also delete SwarmTask nodes (default: true, only applies when swarmId is provided)'),
|
|
71
92
|
keepTypes: z
|
|
72
93
|
.array(z.string())
|
|
73
94
|
.optional()
|
|
@@ -75,7 +96,7 @@ export const createSwarmCleanupTool = (server) => {
|
|
|
75
96
|
.describe('Pheromone types to preserve (default: ["warning"])'),
|
|
76
97
|
dryRun: z.boolean().optional().default(false).describe('Preview what would be deleted without deleting'),
|
|
77
98
|
},
|
|
78
|
-
}, async ({ projectId, swarmId, agentId, all = false, keepTypes = ['warning'], dryRun = false }) => {
|
|
99
|
+
}, async ({ projectId, swarmId, agentId, all = false, includeTasks = true, keepTypes = ['warning'], dryRun = false }) => {
|
|
79
100
|
const neo4jService = new Neo4jService();
|
|
80
101
|
// Resolve project ID
|
|
81
102
|
const projectResult = await resolveProjectIdOrError(projectId, neo4jService);
|
|
@@ -98,52 +119,83 @@ export const createSwarmCleanupTool = (server) => {
|
|
|
98
119
|
dryRun,
|
|
99
120
|
});
|
|
100
121
|
const params = { projectId: resolvedProjectId, keepTypes };
|
|
101
|
-
let
|
|
102
|
-
let
|
|
122
|
+
let pheromoneDeleteQuery;
|
|
123
|
+
let pheromoneCountQuery;
|
|
103
124
|
let mode;
|
|
104
125
|
if (swarmId) {
|
|
105
126
|
params.swarmId = swarmId;
|
|
106
|
-
|
|
107
|
-
|
|
127
|
+
pheromoneDeleteQuery = CLEANUP_PHEROMONES_BY_SWARM_QUERY;
|
|
128
|
+
pheromoneCountQuery = COUNT_PHEROMONES_BY_SWARM_QUERY;
|
|
108
129
|
mode = 'swarm';
|
|
109
130
|
}
|
|
110
131
|
else if (agentId) {
|
|
111
132
|
params.agentId = agentId;
|
|
112
|
-
|
|
113
|
-
|
|
133
|
+
pheromoneDeleteQuery = CLEANUP_BY_AGENT_QUERY;
|
|
134
|
+
pheromoneCountQuery = COUNT_BY_AGENT_QUERY;
|
|
114
135
|
mode = 'agent';
|
|
115
136
|
}
|
|
116
137
|
else {
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
pheromoneDeleteQuery = CLEANUP_ALL_QUERY;
|
|
139
|
+
pheromoneCountQuery = COUNT_ALL_QUERY;
|
|
119
140
|
mode = 'all';
|
|
120
141
|
}
|
|
121
142
|
if (dryRun) {
|
|
122
|
-
const
|
|
123
|
-
const
|
|
143
|
+
const pheromoneResult = await neo4jService.run(pheromoneCountQuery, params);
|
|
144
|
+
const pheromoneCount = pheromoneResult[0]?.count ?? 0;
|
|
145
|
+
let taskCount = 0;
|
|
146
|
+
let taskStatuses = [];
|
|
147
|
+
if (swarmId && includeTasks) {
|
|
148
|
+
const taskResult = await neo4jService.run(COUNT_TASKS_BY_SWARM_QUERY, params);
|
|
149
|
+
taskCount = taskResult[0]?.count ?? 0;
|
|
150
|
+
taskCount = typeof taskCount === 'object' && 'toNumber' in taskCount ? taskCount.toNumber() : taskCount;
|
|
151
|
+
taskStatuses = taskResult[0]?.statuses ?? [];
|
|
152
|
+
}
|
|
124
153
|
return createSuccessResponse(JSON.stringify({
|
|
125
154
|
success: true,
|
|
126
155
|
dryRun: true,
|
|
127
156
|
mode,
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
157
|
+
pheromones: {
|
|
158
|
+
wouldDelete: typeof pheromoneCount === 'object' && 'toNumber' in pheromoneCount ? pheromoneCount.toNumber() : pheromoneCount,
|
|
159
|
+
agents: pheromoneResult[0]?.agents ?? [],
|
|
160
|
+
types: pheromoneResult[0]?.types ?? [],
|
|
161
|
+
},
|
|
162
|
+
tasks: swarmId && includeTasks ? {
|
|
163
|
+
wouldDelete: taskCount,
|
|
164
|
+
statuses: taskStatuses,
|
|
165
|
+
} : null,
|
|
132
166
|
keepTypes,
|
|
133
167
|
projectId: resolvedProjectId,
|
|
134
168
|
}));
|
|
135
169
|
}
|
|
136
|
-
|
|
137
|
-
const
|
|
170
|
+
// Delete pheromones
|
|
171
|
+
const pheromoneResult = await neo4jService.run(pheromoneDeleteQuery, params);
|
|
172
|
+
const pheromonesDeleted = pheromoneResult[0]?.deleted ?? 0;
|
|
173
|
+
// Delete tasks if swarmId provided and includeTasks is true
|
|
174
|
+
let tasksDeleted = 0;
|
|
175
|
+
let taskStatuses = [];
|
|
176
|
+
if (swarmId && includeTasks) {
|
|
177
|
+
const taskResult = await neo4jService.run(CLEANUP_TASKS_BY_SWARM_QUERY, params);
|
|
178
|
+
tasksDeleted = taskResult[0]?.deleted ?? 0;
|
|
179
|
+
tasksDeleted = typeof tasksDeleted === 'object' && 'toNumber' in tasksDeleted ? tasksDeleted.toNumber() : tasksDeleted;
|
|
180
|
+
taskStatuses = taskResult[0]?.statuses ?? [];
|
|
181
|
+
}
|
|
138
182
|
return createSuccessResponse(JSON.stringify({
|
|
139
183
|
success: true,
|
|
140
184
|
mode,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
185
|
+
pheromones: {
|
|
186
|
+
deleted: typeof pheromonesDeleted === 'object' && 'toNumber' in pheromonesDeleted ? pheromonesDeleted.toNumber() : pheromonesDeleted,
|
|
187
|
+
agents: pheromoneResult[0]?.agents ?? [],
|
|
188
|
+
types: pheromoneResult[0]?.types ?? [],
|
|
189
|
+
},
|
|
190
|
+
tasks: swarmId && includeTasks ? {
|
|
191
|
+
deleted: tasksDeleted,
|
|
192
|
+
statuses: taskStatuses,
|
|
193
|
+
} : null,
|
|
145
194
|
keepTypes,
|
|
146
195
|
projectId: resolvedProjectId,
|
|
196
|
+
message: swarmId && includeTasks
|
|
197
|
+
? `Cleaned up ${pheromonesDeleted} pheromones and ${tasksDeleted} tasks`
|
|
198
|
+
: `Cleaned up ${pheromonesDeleted} pheromones`,
|
|
147
199
|
}));
|
|
148
200
|
}
|
|
149
201
|
catch (error) {
|
|
@@ -352,33 +352,129 @@ export const createSwarmOrchestrateTool = (server) => {
|
|
|
352
352
|
*/
|
|
353
353
|
function generateWorkerInstructions(swarmId, projectId, maxAgents, taskCount) {
|
|
354
354
|
const recommendedAgents = Math.min(maxAgents, Math.ceil(taskCount / 2), taskCount);
|
|
355
|
-
|
|
356
|
-
|
|
355
|
+
// Generate unique agent IDs for each worker
|
|
356
|
+
const agentIds = Array.from({ length: recommendedAgents }, (_, i) => `${swarmId}_worker_${i + 1}`);
|
|
357
|
+
const workerPrompt = `You are a swarm worker agent.
|
|
358
|
+
- Agent ID: {AGENT_ID}
|
|
359
|
+
- Swarm ID: ${swarmId}
|
|
360
|
+
- Project: ${projectId}
|
|
357
361
|
|
|
358
|
-
|
|
362
|
+
## CRITICAL RULES
|
|
363
|
+
1. NEVER fabricate node IDs - get them from graph tool responses
|
|
364
|
+
2. ALWAYS use the blackboard task queue (swarm_claim_task, swarm_complete_task)
|
|
365
|
+
3. Use graph tools (traverse_from_node, search_codebase) to understand context
|
|
366
|
+
4. Exit when swarm_claim_task returns "no_tasks"
|
|
359
367
|
|
|
360
|
-
|
|
361
|
-
Task({
|
|
362
|
-
subagent_type: "general-purpose",
|
|
363
|
-
prompt: "You are a swarm worker for swarm ${swarmId}. Project: ${projectId}.
|
|
368
|
+
## WORKFLOW - Follow these steps exactly:
|
|
364
369
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
+
### Step 1: Claim a task from the blackboard
|
|
371
|
+
swarm_claim_task({
|
|
372
|
+
projectId: "${projectId}",
|
|
373
|
+
swarmId: "${swarmId}",
|
|
374
|
+
agentId: "{AGENT_ID}"
|
|
375
|
+
})
|
|
376
|
+
// If returns "no_tasks" → exit, swarm is complete
|
|
377
|
+
// Otherwise you now own the returned task
|
|
370
378
|
|
|
371
|
-
|
|
372
|
-
|
|
379
|
+
### Step 2: Start working and check for conflicts
|
|
380
|
+
swarm_claim_task({
|
|
381
|
+
projectId: "${projectId}",
|
|
382
|
+
swarmId: "${swarmId}",
|
|
383
|
+
agentId: "{AGENT_ID}",
|
|
384
|
+
taskId: "<TASK_ID_FROM_STEP_1>",
|
|
385
|
+
action: "start"
|
|
373
386
|
})
|
|
387
|
+
|
|
388
|
+
// Check if another agent is working on related code
|
|
389
|
+
swarm_sense({
|
|
390
|
+
projectId: "${projectId}",
|
|
391
|
+
swarmId: "${swarmId}",
|
|
392
|
+
types: ["modifying", "warning"],
|
|
393
|
+
excludeAgentId: "{AGENT_ID}"
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
### Step 3: Understand the code context (USE GRAPH TOOLS!)
|
|
397
|
+
// Use traverse_from_node to see relationships and callers
|
|
398
|
+
traverse_from_node({
|
|
399
|
+
projectId: "${projectId}",
|
|
400
|
+
filePath: "<TARGET_FILE_FROM_TASK>",
|
|
401
|
+
maxDepth: 2,
|
|
402
|
+
includeCode: true
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
// Or search for related code
|
|
406
|
+
search_codebase({
|
|
407
|
+
projectId: "${projectId}",
|
|
408
|
+
query: "<WHAT_YOU_NEED_TO_UNDERSTAND>"
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
### Step 4: Do the work
|
|
412
|
+
- Use Read tool for full source code of files to modify
|
|
413
|
+
- Use Edit tool to make changes
|
|
414
|
+
- Mark nodes you're modifying:
|
|
415
|
+
swarm_pheromone({
|
|
416
|
+
projectId: "${projectId}",
|
|
417
|
+
nodeId: "<NODE_ID_FROM_GRAPH>",
|
|
418
|
+
type: "modifying",
|
|
419
|
+
agentId: "{AGENT_ID}",
|
|
420
|
+
swarmId: "${swarmId}"
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
### Step 5: Complete the task via blackboard
|
|
424
|
+
swarm_pheromone({
|
|
425
|
+
projectId: "${projectId}",
|
|
426
|
+
nodeId: "<NODE_ID>",
|
|
427
|
+
type: "completed",
|
|
428
|
+
agentId: "{AGENT_ID}",
|
|
429
|
+
swarmId: "${swarmId}",
|
|
430
|
+
data: { summary: "<WHAT_YOU_DID>" }
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
swarm_complete_task({
|
|
434
|
+
projectId: "${projectId}",
|
|
435
|
+
taskId: "<TASK_ID>",
|
|
436
|
+
agentId: "{AGENT_ID}",
|
|
437
|
+
action: "complete",
|
|
438
|
+
summary: "<DESCRIBE_WHAT_YOU_DID>",
|
|
439
|
+
filesChanged: ["<LIST_OF_FILES_YOU_MODIFIED>"]
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
### Step 6: Loop back to Step 1
|
|
443
|
+
Claim the next available task. Continue until no tasks remain.
|
|
444
|
+
|
|
445
|
+
## IF YOU GET STUCK
|
|
446
|
+
swarm_complete_task({
|
|
447
|
+
projectId: "${projectId}",
|
|
448
|
+
taskId: "<TASK_ID>",
|
|
449
|
+
agentId: "{AGENT_ID}",
|
|
450
|
+
action: "fail",
|
|
451
|
+
reason: "<WHY_YOU_ARE_STUCK>",
|
|
452
|
+
retryable: true
|
|
453
|
+
})
|
|
454
|
+
Then claim another task.`;
|
|
455
|
+
const taskCalls = agentIds.map(agentId => {
|
|
456
|
+
const prompt = workerPrompt.replace(/\{AGENT_ID\}/g, agentId);
|
|
457
|
+
return `Task({
|
|
458
|
+
subagent_type: "general-purpose",
|
|
459
|
+
run_in_background: false,
|
|
460
|
+
prompt: \`${prompt}\`
|
|
461
|
+
})`;
|
|
462
|
+
}).join('\n\n');
|
|
463
|
+
return `
|
|
464
|
+
## Worker Agent Instructions
|
|
465
|
+
|
|
466
|
+
Spawn ${recommendedAgents} worker agent(s) IN PARALLEL (all Task calls in ONE message):
|
|
467
|
+
|
|
468
|
+
\`\`\`javascript
|
|
469
|
+
${taskCalls}
|
|
374
470
|
\`\`\`
|
|
375
471
|
|
|
376
|
-
|
|
472
|
+
**CRITICAL:** Include ALL ${recommendedAgents} Task calls in a single message to run them in parallel.
|
|
377
473
|
|
|
378
474
|
## Monitoring Progress
|
|
379
475
|
|
|
380
476
|
Check swarm progress:
|
|
381
|
-
\`\`\`
|
|
477
|
+
\`\`\`javascript
|
|
382
478
|
swarm_get_tasks({
|
|
383
479
|
projectId: "${projectId}",
|
|
384
480
|
swarmId: "${swarmId}",
|
|
@@ -386,9 +482,9 @@ swarm_get_tasks({
|
|
|
386
482
|
})
|
|
387
483
|
\`\`\`
|
|
388
484
|
|
|
389
|
-
## Cleanup (after
|
|
485
|
+
## Cleanup (after all workers complete)
|
|
390
486
|
|
|
391
|
-
\`\`\`
|
|
487
|
+
\`\`\`javascript
|
|
392
488
|
swarm_cleanup({
|
|
393
489
|
projectId: "${projectId}",
|
|
394
490
|
swarmId: "${swarmId}"
|
package/package.json
CHANGED