@grec0/memory-bank-mcp 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/agentBoard.js +124 -14
- package/dist/common/registryManager.js +56 -0
- package/dist/common/sessionLogger.js +45 -0
- package/dist/common/version.js +1 -1
- package/dist/index.js +54 -1
- package/dist/tools/delegateTask.js +59 -0
- package/dist/tools/discoverProjects.js +23 -0
- package/dist/tools/getProjectDocs.js +29 -1
- package/dist/tools/initializeMemoryBank.js +10 -0
- package/dist/tools/manageAgents.js +15 -3
- package/dist/tools/searchMemory.js +28 -1
- package/package.json +1 -1
|
@@ -10,6 +10,81 @@ export class AgentBoard {
|
|
|
10
10
|
this.projectId = projectId;
|
|
11
11
|
this.lockManager = new LockManager(basePath);
|
|
12
12
|
}
|
|
13
|
+
async getBoardContent() {
|
|
14
|
+
await this.ensureBoardExists();
|
|
15
|
+
return await fs.readFile(this.getBoardPath(), 'utf-8');
|
|
16
|
+
}
|
|
17
|
+
// --- Task Management Methods ---
|
|
18
|
+
async createTask(title, fromAgentId, assignedTo, description) {
|
|
19
|
+
const taskId = `TASK-${Date.now().toString().slice(-6)}`;
|
|
20
|
+
await this.updateBoard((content) => {
|
|
21
|
+
const tasks = this.parseTable(content, 'Pending Tasks');
|
|
22
|
+
const now = new Date().toISOString();
|
|
23
|
+
// Columns: ID, Title, Assigned To, From, Status, Created At
|
|
24
|
+
tasks.push([taskId, title, assignedTo, fromAgentId, 'PENDING', now]);
|
|
25
|
+
return this.updateTable(content, 'Pending Tasks', ['ID', 'Title', 'Assigned To', 'From', 'Status', 'Created At'], tasks);
|
|
26
|
+
});
|
|
27
|
+
await this.logMessage(fromAgentId, `Created task ${taskId}: ${title}`);
|
|
28
|
+
return taskId;
|
|
29
|
+
}
|
|
30
|
+
async createExternalTask(title, fromProject, context) {
|
|
31
|
+
const taskId = `EXT-${Date.now().toString().slice(-6)}`;
|
|
32
|
+
await this.updateBoard((content) => {
|
|
33
|
+
const requests = this.parseTable(content, 'External Requests');
|
|
34
|
+
const now = new Date().toISOString();
|
|
35
|
+
// Columns: ID, Title, From Project, Context, Status, Received At
|
|
36
|
+
requests.push([taskId, title, fromProject, context, 'PENDING', now]);
|
|
37
|
+
return this.updateTable(content, 'External Requests', ['ID', 'Title', 'From Project', 'Context', 'Status', 'Received At'], requests);
|
|
38
|
+
});
|
|
39
|
+
await this.logMessage('SYSTEM', `Received external prompt from project ${fromProject}: ${title}`);
|
|
40
|
+
return taskId;
|
|
41
|
+
}
|
|
42
|
+
async completeTask(taskId, agentId) {
|
|
43
|
+
await this.updateBoard((content) => {
|
|
44
|
+
let tasks = this.parseTable(content, 'Pending Tasks');
|
|
45
|
+
const initialCount = tasks.length;
|
|
46
|
+
tasks = tasks.filter(t => t[0] !== taskId);
|
|
47
|
+
if (tasks.length < initialCount) {
|
|
48
|
+
return this.updateTable(content, 'Pending Tasks', ['ID', 'Title', 'Assigned To', 'From', 'Status', 'Created At'], tasks);
|
|
49
|
+
}
|
|
50
|
+
// Check external requests if not found in pending
|
|
51
|
+
let requests = this.parseTable(content, 'External Requests');
|
|
52
|
+
requests = requests.filter(t => t[0] !== taskId);
|
|
53
|
+
return this.updateTable(content, 'External Requests', ['ID', 'Title', 'From Project', 'Context', 'Status', 'Received At'], requests);
|
|
54
|
+
});
|
|
55
|
+
await this.logMessage(agentId, `Completed task ${taskId}`);
|
|
56
|
+
}
|
|
57
|
+
async logMessage(agentId, message) {
|
|
58
|
+
await this.updateBoard((content) => {
|
|
59
|
+
const lines = content.split('\n');
|
|
60
|
+
let msgSectionIdx = -1;
|
|
61
|
+
for (let i = 0; i < lines.length; i++) {
|
|
62
|
+
if (lines[i].trim().startsWith('## Agent Messages')) {
|
|
63
|
+
msgSectionIdx = i;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
|
68
|
+
const msgLine = `- [${timestamp}] **${agentId}**: ${message}`;
|
|
69
|
+
if (msgSectionIdx !== -1) {
|
|
70
|
+
// Determine insertion point (after header)
|
|
71
|
+
lines.splice(msgSectionIdx + 1, 0, msgLine);
|
|
72
|
+
// Truncate logs if too long (keep last 20)
|
|
73
|
+
let nextHeaderIdx = -1;
|
|
74
|
+
for (let j = msgSectionIdx + 2; j < lines.length; j++) {
|
|
75
|
+
if (lines[j].startsWith('## ')) {
|
|
76
|
+
nextHeaderIdx = j;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const endOfMessages = nextHeaderIdx === -1 ? lines.length : nextHeaderIdx;
|
|
81
|
+
if (endOfMessages - (msgSectionIdx + 1) > 20) {
|
|
82
|
+
lines.splice(endOfMessages - 1, 1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return lines.join('\n');
|
|
86
|
+
});
|
|
87
|
+
}
|
|
13
88
|
getBoardPath() {
|
|
14
89
|
return path.join(this.basePath, '.memorybank', 'projects', this.projectId, 'docs', 'agentBoard.md');
|
|
15
90
|
}
|
|
@@ -22,8 +97,16 @@ export class AgentBoard {
|
|
|
22
97
|
const initialContent = `# Multi-Agent Board
|
|
23
98
|
|
|
24
99
|
## Active Agents
|
|
25
|
-
| Agent ID | Status | Current Focus | Last Heartbeat |
|
|
26
|
-
|
|
100
|
+
| Agent ID | Status | Current Focus | Session ID | Last Heartbeat |
|
|
101
|
+
|---|---|---|---|---|
|
|
102
|
+
|
|
103
|
+
## Pending Tasks
|
|
104
|
+
| ID | Title | Assigned To | From | Status | Created At |
|
|
105
|
+
|---|---|---|---|---|---|
|
|
106
|
+
|
|
107
|
+
## External Requests
|
|
108
|
+
| ID | Title | From Project | Context | Status | Received At |
|
|
109
|
+
|---|---|---|---|---|---|
|
|
27
110
|
|
|
28
111
|
## File Locks
|
|
29
112
|
| File Pattern | Claimed By | Since |
|
|
@@ -36,34 +119,64 @@ export class AgentBoard {
|
|
|
36
119
|
await fs.writeFile(boardPath, initialContent, 'utf-8');
|
|
37
120
|
}
|
|
38
121
|
}
|
|
39
|
-
async registerAgent(agentId) {
|
|
122
|
+
async registerAgent(agentId, sessionId) {
|
|
40
123
|
await this.updateBoard((content) => {
|
|
41
124
|
const agents = this.parseTable(content, 'Active Agents');
|
|
42
125
|
const existing = agents.findIndex(a => a[0]?.trim() === agentId);
|
|
43
126
|
const now = new Date().toISOString();
|
|
127
|
+
const session = sessionId || (existing >= 0 ? (agents[existing][3] || '') : ''); // Use col 3 as SessionID based on previous read
|
|
44
128
|
if (existing >= 0) {
|
|
45
|
-
|
|
129
|
+
// Check if it's already 5 cols or 4
|
|
130
|
+
if (agents[existing].length < 5) {
|
|
131
|
+
agents[existing] = [agentId, 'ACTIVE', '-', session, now];
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// preserve existing fields if needed, but update timestamp
|
|
135
|
+
agents[existing] = [agentId, 'ACTIVE', '-', session, now];
|
|
136
|
+
}
|
|
46
137
|
}
|
|
47
138
|
else {
|
|
48
|
-
agents.push([agentId, 'ACTIVE', '-', now]);
|
|
139
|
+
agents.push([agentId, 'ACTIVE', '-', session, now]);
|
|
49
140
|
}
|
|
50
|
-
return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Last Heartbeat'], agents);
|
|
141
|
+
return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Session ID', 'Last Heartbeat'], agents);
|
|
51
142
|
});
|
|
52
143
|
}
|
|
53
144
|
async updateStatus(agentId, status, focus) {
|
|
54
145
|
await this.updateBoard((content) => {
|
|
55
|
-
|
|
146
|
+
let agents = this.parseTable(content, 'Active Agents');
|
|
147
|
+
// Migration: Ensure 5 columns
|
|
148
|
+
agents = agents.map(row => {
|
|
149
|
+
if (row.length === 4) {
|
|
150
|
+
return [row[0], row[1], row[2], '', row[3]];
|
|
151
|
+
}
|
|
152
|
+
return row;
|
|
153
|
+
});
|
|
56
154
|
const idx = agents.findIndex(a => a[0]?.trim() === agentId);
|
|
57
155
|
const now = new Date().toISOString();
|
|
58
156
|
if (idx >= 0) {
|
|
59
|
-
|
|
157
|
+
// Keep existing session ID
|
|
158
|
+
const currentSession = agents[idx][3] || '';
|
|
159
|
+
agents[idx] = [agentId, status, focus, currentSession, now];
|
|
60
160
|
}
|
|
61
161
|
else {
|
|
62
|
-
agents.push([agentId, status, focus, now]);
|
|
162
|
+
agents.push([agentId, status, focus, '', now]);
|
|
63
163
|
}
|
|
64
|
-
return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Last Heartbeat'], agents);
|
|
164
|
+
return this.updateTable(content, 'Active Agents', ['Agent ID', 'Status', 'Current Focus', 'Session ID', 'Last Heartbeat'], agents);
|
|
65
165
|
});
|
|
66
166
|
}
|
|
167
|
+
async getSessionId(agentId) {
|
|
168
|
+
const content = await this.getBoardContent();
|
|
169
|
+
const agents = this.parseTable(content, 'Active Agents');
|
|
170
|
+
const agent = agents.find(a => a[0]?.trim() === agentId);
|
|
171
|
+
if (agent) {
|
|
172
|
+
// Handle 5 cols
|
|
173
|
+
if (agent.length >= 5)
|
|
174
|
+
return agent[3].trim();
|
|
175
|
+
// Handle 4 cols (legacy) - no session ID
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
67
180
|
async claimResource(agentId, resource) {
|
|
68
181
|
let success = false;
|
|
69
182
|
await this.updateBoard((content) => {
|
|
@@ -95,10 +208,7 @@ export class AgentBoard {
|
|
|
95
208
|
return this.updateTable(content, 'File Locks', ['File Pattern', 'Claimed By', 'Since'], locks);
|
|
96
209
|
});
|
|
97
210
|
}
|
|
98
|
-
|
|
99
|
-
await this.ensureBoardExists();
|
|
100
|
-
return await fs.readFile(this.getBoardPath(), 'utf-8');
|
|
101
|
-
}
|
|
211
|
+
// [Duplicate getBoardContent removed]
|
|
102
212
|
// --- Helpers ---
|
|
103
213
|
async updateBoard(mutator) {
|
|
104
214
|
await this.ensureBoardExists();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
export class RegistryManager {
|
|
5
|
+
globalPath;
|
|
6
|
+
constructor() {
|
|
7
|
+
this.globalPath = path.join(os.homedir(), '.memorybank', 'global_registry.json');
|
|
8
|
+
}
|
|
9
|
+
async ensureRegistry() {
|
|
10
|
+
try {
|
|
11
|
+
await fs.mkdir(path.dirname(this.globalPath), { recursive: true });
|
|
12
|
+
const content = await fs.readFile(this.globalPath, 'utf-8');
|
|
13
|
+
return JSON.parse(content);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return { projects: [] };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async saveRegistry(registry) {
|
|
20
|
+
await fs.writeFile(this.globalPath, JSON.stringify(registry, null, 2), 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
async registerProject(projectId, workspacePath, description, keywords = []) {
|
|
23
|
+
const registry = await this.ensureRegistry();
|
|
24
|
+
const idx = registry.projects.findIndex(p => p.projectId === projectId);
|
|
25
|
+
// Preserve existing description/keywords if not provided
|
|
26
|
+
const existing = idx >= 0 ? registry.projects[idx] : null;
|
|
27
|
+
const card = {
|
|
28
|
+
projectId,
|
|
29
|
+
path: workspacePath,
|
|
30
|
+
description: description || existing?.description || '',
|
|
31
|
+
keywords: keywords.length > 0 ? keywords : (existing?.keywords || []),
|
|
32
|
+
lastActive: new Date().toISOString(),
|
|
33
|
+
status: 'ACTIVE'
|
|
34
|
+
};
|
|
35
|
+
if (idx >= 0) {
|
|
36
|
+
registry.projects[idx] = card;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
registry.projects.push(card);
|
|
40
|
+
}
|
|
41
|
+
await this.saveRegistry(registry);
|
|
42
|
+
}
|
|
43
|
+
async discoverProjects(query) {
|
|
44
|
+
const registry = await this.ensureRegistry();
|
|
45
|
+
if (!query || query.trim() === '')
|
|
46
|
+
return registry.projects;
|
|
47
|
+
const q = query.toLowerCase();
|
|
48
|
+
return registry.projects.filter(p => p.projectId.toLowerCase().includes(q) ||
|
|
49
|
+
(p.description && p.description.toLowerCase().includes(q)) ||
|
|
50
|
+
p.keywords.some(k => k.toLowerCase().includes(q)));
|
|
51
|
+
}
|
|
52
|
+
async getProject(projectId) {
|
|
53
|
+
const registry = await this.ensureRegistry();
|
|
54
|
+
return registry.projects.find(p => p.projectId === projectId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
export class SessionLogger {
|
|
4
|
+
projectPath;
|
|
5
|
+
constructor(projectPath) {
|
|
6
|
+
this.projectPath = projectPath;
|
|
7
|
+
}
|
|
8
|
+
getSessionDir(projectId) {
|
|
9
|
+
return path.join(this.projectPath, '.memorybank', 'projects', projectId, 'sessions');
|
|
10
|
+
}
|
|
11
|
+
getSessionFilePath(projectId, sessionId) {
|
|
12
|
+
return path.join(this.getSessionDir(projectId), `${sessionId}.jsonl`);
|
|
13
|
+
}
|
|
14
|
+
async logSessionEvent(projectId, sessionId, event) {
|
|
15
|
+
if (!sessionId)
|
|
16
|
+
return; // No logging if no session ID provided
|
|
17
|
+
const sessionDir = this.getSessionDir(projectId);
|
|
18
|
+
try {
|
|
19
|
+
await fs.mkdir(sessionDir, { recursive: true });
|
|
20
|
+
const filePath = this.getSessionFilePath(projectId, sessionId);
|
|
21
|
+
// Append line to JSONL file
|
|
22
|
+
const line = JSON.stringify(event) + '\n';
|
|
23
|
+
await fs.appendFile(filePath, line, 'utf-8');
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.error(`[SessionLogger] Error logging event for session ${sessionId}:`, error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async getSessionHistory(projectId, sessionId) {
|
|
30
|
+
const filePath = this.getSessionFilePath(projectId, sessionId);
|
|
31
|
+
try {
|
|
32
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
33
|
+
return content.split('\n')
|
|
34
|
+
.filter(line => line.trim().length > 0)
|
|
35
|
+
.map(line => JSON.parse(line));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error.code === 'ENOENT') {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const sessionLogger = new SessionLogger(process.cwd());
|
package/dist/common/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Version of the MCP Kanban server
|
|
2
|
-
export const VERSION = "0.1.
|
|
2
|
+
export const VERSION = "0.1.18";
|
package/dist/index.js
CHANGED
|
@@ -80,12 +80,16 @@ import { updateContext, updateContextToolDefinition } from "./tools/updateContex
|
|
|
80
80
|
import { recordDecision, recordDecisionToolDefinition } from "./tools/recordDecision.js";
|
|
81
81
|
import { trackProgress, trackProgressToolDefinition } from "./tools/trackProgress.js";
|
|
82
82
|
import { manageAgentsTool, manageAgentsToolDefinition } from "./tools/manageAgents.js";
|
|
83
|
+
import { discoverProjectsTool, discoverProjectsToolDefinition } from "./tools/discoverProjects.js";
|
|
84
|
+
import { delegateTaskTool, delegateTaskToolDefinition } from "./tools/delegateTask.js";
|
|
85
|
+
import { RegistryManager } from "./common/registryManager.js";
|
|
83
86
|
import { VERSION } from "./common/version.js";
|
|
84
87
|
// Global services
|
|
85
88
|
let embeddingService;
|
|
86
89
|
let vectorStore;
|
|
87
90
|
let indexManager;
|
|
88
91
|
let projectKnowledgeService;
|
|
92
|
+
let registryManager;
|
|
89
93
|
let workspaceRoot;
|
|
90
94
|
// Create the MCP Server
|
|
91
95
|
const server = new McpServer({
|
|
@@ -155,6 +159,10 @@ server.tool("memorybank_search", "Busca código relevante mediante búsqueda sem
|
|
|
155
159
|
.string()
|
|
156
160
|
.optional()
|
|
157
161
|
.describe("Filtrar resultados por lenguaje de programación (ej: 'typescript', 'python')"),
|
|
162
|
+
agentId: z
|
|
163
|
+
.string()
|
|
164
|
+
.optional()
|
|
165
|
+
.describe("Identificador del agente (para logging de sesión)"),
|
|
158
166
|
}, async (args) => {
|
|
159
167
|
const result = await searchMemory({
|
|
160
168
|
projectId: args.projectId,
|
|
@@ -163,7 +171,8 @@ server.tool("memorybank_search", "Busca código relevante mediante búsqueda sem
|
|
|
163
171
|
minScore: args.minScore,
|
|
164
172
|
filterByFile: args.filterByFile,
|
|
165
173
|
filterByLanguage: args.filterByLanguage,
|
|
166
|
-
|
|
174
|
+
agentId: args.agentId,
|
|
175
|
+
}, indexManager, workspaceRoot);
|
|
167
176
|
return {
|
|
168
177
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
169
178
|
};
|
|
@@ -494,6 +503,7 @@ server.tool(manageAgentsToolDefinition.name, manageAgentsToolDefinition.descript
|
|
|
494
503
|
projectId: z.string().describe("Identificador único del proyecto (OBLIGATORIO)"),
|
|
495
504
|
action: z.enum(["register", "update_status", "claim_resource", "release_resource", "get_board"]).describe("Acción a realizar"),
|
|
496
505
|
agentId: z.string().optional().describe("Identificador del agente (ej: 'dev-agent-1'). Requerido para escrituras."),
|
|
506
|
+
sessionId: z.string().optional().describe("UUID de sesión del agente para tracking de contexto."),
|
|
497
507
|
status: z.string().optional().describe("Estado del agente (para update_status)."),
|
|
498
508
|
focus: z.string().optional().describe("Tarea o fichero en el que se enfoca (para update_status)."),
|
|
499
509
|
resource: z.string().optional().describe("Identificador del recurso a bloquear (ej: 'src/auth/')."),
|
|
@@ -506,6 +516,7 @@ server.tool(manageAgentsToolDefinition.name, manageAgentsToolDefinition.descript
|
|
|
506
516
|
projectId: args.projectId,
|
|
507
517
|
action: args.action,
|
|
508
518
|
agentId: args.agentId,
|
|
519
|
+
sessionId: args.sessionId,
|
|
509
520
|
status: args.status,
|
|
510
521
|
focus: args.focus,
|
|
511
522
|
resource: args.resource
|
|
@@ -753,6 +764,18 @@ async function validateEnvironment() {
|
|
|
753
764
|
indexManager.setProjectKnowledgeService(projectKnowledgeService);
|
|
754
765
|
indexManager.setAutoUpdateDocs(autoUpdateDocs);
|
|
755
766
|
console.error("✓ Project Knowledge service connected to Index Manager");
|
|
767
|
+
// Initialize Registry Manager and register current project
|
|
768
|
+
try {
|
|
769
|
+
registryManager = new RegistryManager();
|
|
770
|
+
// Assuming user defines project ID in env or generic default
|
|
771
|
+
// We will try to read it from an existing .memorybank config if possible, or pass it later
|
|
772
|
+
// But here we can't easily know the projectId if it hasn't been initialized by the user.
|
|
773
|
+
// However, if the user calls initialize_memorybank, we should register it then.
|
|
774
|
+
console.error("✓ Registry manager initialized");
|
|
775
|
+
}
|
|
776
|
+
catch (regError) {
|
|
777
|
+
console.error(`⚠ Warning: Registry Manager service not available: ${regError}`);
|
|
778
|
+
}
|
|
756
779
|
}
|
|
757
780
|
catch (error) {
|
|
758
781
|
console.error(`⚠ Warning: Project Knowledge service not available: ${error}`);
|
|
@@ -766,6 +789,36 @@ async function validateEnvironment() {
|
|
|
766
789
|
console.error("\n✓ All services ready");
|
|
767
790
|
console.error("");
|
|
768
791
|
}
|
|
792
|
+
// Tool: Discover Projects
|
|
793
|
+
server.tool(discoverProjectsToolDefinition.name, discoverProjectsToolDefinition.description, {
|
|
794
|
+
query: z.string().optional().describe("Término de búsqueda (por ID, descripción o keywords)")
|
|
795
|
+
}, async (args) => {
|
|
796
|
+
const result = await discoverProjectsTool({
|
|
797
|
+
query: args.query
|
|
798
|
+
});
|
|
799
|
+
return {
|
|
800
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
801
|
+
};
|
|
802
|
+
});
|
|
803
|
+
// Tool: Delegate Task
|
|
804
|
+
server.tool(delegateTaskToolDefinition.name, delegateTaskToolDefinition.description, {
|
|
805
|
+
projectId: z.string().describe("ID del proyecto origen (quien pide)"),
|
|
806
|
+
targetProjectId: z.string().describe("ID del proyecto destino (quien debe hacer el trabajo)"),
|
|
807
|
+
title: z.string().describe("Título corto de la tarea"),
|
|
808
|
+
description: z.string().describe("Descripción detallada de lo que se necesita"),
|
|
809
|
+
context: z.string().optional().describe("Contexto técnico adicional para que el agente receptor entienda la tarea")
|
|
810
|
+
}, async (args) => {
|
|
811
|
+
const result = await delegateTaskTool({
|
|
812
|
+
projectId: args.projectId,
|
|
813
|
+
targetProjectId: args.targetProjectId,
|
|
814
|
+
title: args.title,
|
|
815
|
+
description: args.description,
|
|
816
|
+
context: args.context || ''
|
|
817
|
+
});
|
|
818
|
+
return {
|
|
819
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
820
|
+
};
|
|
821
|
+
});
|
|
769
822
|
/**
|
|
770
823
|
* Starts the stdio server
|
|
771
824
|
*/
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { RegistryManager } from '../common/registryManager.js';
|
|
2
|
+
import { AgentBoard } from '../common/agentBoard.js';
|
|
3
|
+
export async function delegateTaskTool(params) {
|
|
4
|
+
const registryManager = new RegistryManager();
|
|
5
|
+
const targetProject = await registryManager.getProject(params.targetProjectId);
|
|
6
|
+
if (!targetProject) {
|
|
7
|
+
return {
|
|
8
|
+
success: false,
|
|
9
|
+
message: `Target project '${params.targetProjectId}' not found in global registry.`
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
// Initialize board for the TARGET project path
|
|
14
|
+
// We use targetProject.path as the workspace root for that board
|
|
15
|
+
const targetBoard = new AgentBoard(targetProject.path, targetProject.projectId);
|
|
16
|
+
const taskId = await targetBoard.createExternalTask(params.title, params.projectId, `${params.description}\n\nContext:\n${params.context}`);
|
|
17
|
+
return {
|
|
18
|
+
success: true,
|
|
19
|
+
taskId,
|
|
20
|
+
message: `Task successfully delegated to project '${params.targetProjectId}' (Task ID: ${taskId})`
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return {
|
|
25
|
+
success: false,
|
|
26
|
+
message: `Failed to delegate task: ${error.message}`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const delegateTaskToolDefinition = {
|
|
31
|
+
name: "memorybank_delegate_task",
|
|
32
|
+
description: "Delega una tarea a otro proyecto del ecosistema. Crea una petición externa en el tablero del proyecto destino.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
projectId: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "ID del proyecto origen (quien pide)"
|
|
39
|
+
},
|
|
40
|
+
targetProjectId: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "ID del proyecto destino (quien debe hacer el trabajo)"
|
|
43
|
+
},
|
|
44
|
+
title: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "Título corto de la tarea"
|
|
47
|
+
},
|
|
48
|
+
description: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "Descripción detallada de lo que se necesita"
|
|
51
|
+
},
|
|
52
|
+
context: {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Contexto técnico adicional para que el agente receptor entienda la tarea"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
required: ["projectId", "targetProjectId", "title", "description"]
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RegistryManager } from '../common/registryManager.js';
|
|
2
|
+
export async function discoverProjectsTool(params) {
|
|
3
|
+
const registryManager = new RegistryManager();
|
|
4
|
+
const projects = await registryManager.discoverProjects(params.query);
|
|
5
|
+
return {
|
|
6
|
+
success: true,
|
|
7
|
+
projectCount: projects.length,
|
|
8
|
+
projects: projects
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export const discoverProjectsToolDefinition = {
|
|
12
|
+
name: "memorybank_discover_projects",
|
|
13
|
+
description: "Descubre otros proyectos indexados en el ecosistema Memory Bank local. Útil para coordinar tareas entre proyectos.",
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
query: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "Término de búsqueda (por ID, descripción o keywords)"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* @fileoverview Tool for reading project documentation
|
|
3
3
|
* Retrieves generated markdown documents for project context
|
|
4
4
|
*/
|
|
5
|
+
import { AgentBoard } from "../common/agentBoard.js";
|
|
6
|
+
import { sessionLogger } from "../common/sessionLogger.js";
|
|
5
7
|
const VALID_DOC_TYPES = [
|
|
6
8
|
"projectBrief",
|
|
7
9
|
"productContext",
|
|
@@ -13,7 +15,30 @@ const VALID_DOC_TYPES = [
|
|
|
13
15
|
/**
|
|
14
16
|
* Retrieves project documentation
|
|
15
17
|
*/
|
|
16
|
-
export async function getProjectDocs(params, projectKnowledgeService) {
|
|
18
|
+
export async function getProjectDocs(params, projectKnowledgeService, workspaceRoot) {
|
|
19
|
+
const logSession = async (outputSummary) => {
|
|
20
|
+
if (params.agentId && workspaceRoot) {
|
|
21
|
+
try {
|
|
22
|
+
const board = new AgentBoard(workspaceRoot, params.projectId);
|
|
23
|
+
const sessionId = await board.getSessionId(params.agentId);
|
|
24
|
+
if (sessionId) {
|
|
25
|
+
// Use singleton sessionLogger
|
|
26
|
+
await sessionLogger.logSessionEvent(params.projectId, sessionId, {
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
type: 'read_doc',
|
|
29
|
+
data: {
|
|
30
|
+
document: params.document || 'all',
|
|
31
|
+
format: params.format || 'full',
|
|
32
|
+
summary: outputSummary
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error(`Failed to log doc session: ${error}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
17
42
|
try {
|
|
18
43
|
const projectId = params.projectId;
|
|
19
44
|
const format = params.format || "full";
|
|
@@ -41,6 +66,7 @@ export async function getProjectDocs(params, projectKnowledgeService) {
|
|
|
41
66
|
// Handle summary request
|
|
42
67
|
if (requestedDoc === "summary" || format === "summary") {
|
|
43
68
|
const summary = projectKnowledgeService.getDocumentsSummary(projectId);
|
|
69
|
+
await logSession("Summary of all documents");
|
|
44
70
|
return {
|
|
45
71
|
success: true,
|
|
46
72
|
message: `Retrieved summary of ${stats.documentCount} project documents for "${projectId}".`,
|
|
@@ -51,6 +77,7 @@ export async function getProjectDocs(params, projectKnowledgeService) {
|
|
|
51
77
|
// Handle "all" or no specific document
|
|
52
78
|
if (!requestedDoc || requestedDoc === "all") {
|
|
53
79
|
const documents = projectKnowledgeService.getAllDocuments(projectId);
|
|
80
|
+
await logSession(`All ${documents.length} documents`);
|
|
54
81
|
return {
|
|
55
82
|
success: true,
|
|
56
83
|
message: `Retrieved ${documents.length} project documents for "${projectId}".`,
|
|
@@ -76,6 +103,7 @@ export async function getProjectDocs(params, projectKnowledgeService) {
|
|
|
76
103
|
stats: statsResult,
|
|
77
104
|
};
|
|
78
105
|
}
|
|
106
|
+
await logSession(`Document: ${normalizedDoc}`);
|
|
79
107
|
return {
|
|
80
108
|
success: true,
|
|
81
109
|
message: `Retrieved document: ${normalizedDoc} for project "${projectId}"`,
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from "fs";
|
|
6
6
|
import * as path from "path";
|
|
7
|
+
import { RegistryManager } from "../common/registryManager.js";
|
|
7
8
|
/**
|
|
8
9
|
* Document templates for initialization
|
|
9
10
|
*/
|
|
@@ -295,6 +296,15 @@ export async function initializeMemoryBank(params, storagePath = ".memorybank")
|
|
|
295
296
|
// Create directory structure
|
|
296
297
|
console.error(`Creating directory: ${docsPath}`);
|
|
297
298
|
fs.mkdirSync(docsPath, { recursive: true });
|
|
299
|
+
// Register project globally
|
|
300
|
+
try {
|
|
301
|
+
const registry = new RegistryManager();
|
|
302
|
+
await registry.registerProject(projectId, projectPath, description, projectName ? [projectName] : []);
|
|
303
|
+
console.error(` Global registry updated.`);
|
|
304
|
+
}
|
|
305
|
+
catch (regErr) {
|
|
306
|
+
console.error(` Failed to update global registry: ${regErr}`);
|
|
307
|
+
}
|
|
298
308
|
// Create all template documents
|
|
299
309
|
const documentsCreated = [];
|
|
300
310
|
const documents = {
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { AgentBoard } from '../common/agentBoard.js';
|
|
2
|
+
import * as crypto from 'crypto';
|
|
2
3
|
const WORKSPACE_ROOT = process.cwd(); // Will be overridden by actual workspace logic if passed
|
|
3
4
|
export async function manageAgentsTool(params, workspaceRoot = WORKSPACE_ROOT) {
|
|
4
|
-
const { projectId, action, agentId, status, focus, resource } = params;
|
|
5
|
+
const { projectId, action, agentId, sessionId, status, focus, resource } = params;
|
|
5
6
|
const board = new AgentBoard(workspaceRoot, projectId);
|
|
6
7
|
try {
|
|
7
8
|
switch (action) {
|
|
8
9
|
case 'register':
|
|
9
10
|
if (!agentId)
|
|
10
11
|
throw new Error('agentId is required for register');
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
// Auto-generate session ID if not provided.
|
|
13
|
+
// This abstracts the session management from the agent.
|
|
14
|
+
const effectiveSessionId = sessionId || crypto.randomUUID();
|
|
15
|
+
await board.registerAgent(agentId, effectiveSessionId);
|
|
16
|
+
return {
|
|
17
|
+
success: true,
|
|
18
|
+
message: `Agent ${agentId} registered`,
|
|
19
|
+
sessionId: effectiveSessionId
|
|
20
|
+
};
|
|
13
21
|
case 'update_status':
|
|
14
22
|
if (!agentId)
|
|
15
23
|
throw new Error('agentId is required for update_status');
|
|
@@ -63,6 +71,10 @@ export const manageAgentsToolDefinition = {
|
|
|
63
71
|
type: "string",
|
|
64
72
|
description: "Identificador del agente (ej: 'dev-agent-1'). Requerido para escrituras.",
|
|
65
73
|
},
|
|
74
|
+
sessionId: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Identificador de sesión (opcional, para registro)",
|
|
77
|
+
},
|
|
66
78
|
status: {
|
|
67
79
|
type: "string",
|
|
68
80
|
description: "Estado del agente (para update_status).",
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* @fileoverview Search memory tool for Memory Bank
|
|
3
3
|
* Searches code semantically using vector similarity
|
|
4
4
|
*/
|
|
5
|
+
import { AgentBoard } from "../common/agentBoard.js";
|
|
6
|
+
import { sessionLogger } from "../common/sessionLogger.js";
|
|
5
7
|
/**
|
|
6
8
|
* Searches the Memory Bank for relevant code
|
|
7
9
|
*/
|
|
8
|
-
export async function searchMemory(params, indexManager) {
|
|
10
|
+
export async function searchMemory(params, indexManager, workspaceRoot) {
|
|
9
11
|
try {
|
|
10
12
|
if (!params.query || params.query.trim() === "") {
|
|
11
13
|
return {
|
|
@@ -34,6 +36,31 @@ export async function searchMemory(params, indexManager) {
|
|
|
34
36
|
filterByFile: params.filterByFile,
|
|
35
37
|
filterByLanguage: params.filterByLanguage,
|
|
36
38
|
});
|
|
39
|
+
// Session Logging
|
|
40
|
+
if (params.agentId && workspaceRoot) {
|
|
41
|
+
try {
|
|
42
|
+
const board = new AgentBoard(workspaceRoot, params.projectId);
|
|
43
|
+
const sessionId = await board.getSessionId(params.agentId);
|
|
44
|
+
if (sessionId) {
|
|
45
|
+
await sessionLogger.logSessionEvent(params.projectId, sessionId, {
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
type: 'search',
|
|
48
|
+
data: {
|
|
49
|
+
query: params.query,
|
|
50
|
+
filters: {
|
|
51
|
+
file: params.filterByFile,
|
|
52
|
+
lang: params.filterByLanguage
|
|
53
|
+
},
|
|
54
|
+
resultCount: results.length,
|
|
55
|
+
topResults: results.slice(0, 5).map(r => ({ path: r.filePath, score: r.score }))
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (logError) {
|
|
61
|
+
console.error(`Failed to log search session: ${logError}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
37
64
|
console.error(`Found ${results.length} result(s)`);
|
|
38
65
|
// Format message
|
|
39
66
|
let message = `Found ${results.length} result(s)`;
|