agent-enderun 0.9.3 → 0.9.5
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/.enderun/ENDERUN.md +1 -1
- package/.enderun/PROJECT_MEMORY.md +16 -0
- package/.enderun/STATUS.md +2 -2
- package/.enderun/config.json +1 -1
- package/.enderun/logs/manager.json +17 -0
- package/.enderun/mcp_config.json +4 -2
- package/.enderun/memory-graph/shared-facts.json +1 -1
- package/README.md +171 -99
- package/bin/init-check.js +9 -1
- package/bin/update-contract.js +9 -1
- package/bin/validate-agent-army.js +9 -1
- package/framework-mcp/dist/index.js +43 -225
- package/framework-mcp/dist/tools/definitions.js +109 -0
- package/framework-mcp/dist/tools/file_system/patch_file.js +18 -0
- package/framework-mcp/dist/tools/file_system/read_file.js +7 -0
- package/framework-mcp/dist/tools/file_system/replace_text.js +14 -0
- package/framework-mcp/dist/tools/file_system/write_file.js +9 -0
- package/framework-mcp/dist/tools/framework/get_status.js +5 -0
- package/framework-mcp/dist/tools/framework/orchestrate.js +5 -0
- package/framework-mcp/dist/tools/framework/update_contract_hash.js +5 -0
- package/framework-mcp/dist/tools/framework/update_memory.js +8 -0
- package/framework-mcp/dist/tools/index.js +25 -0
- package/framework-mcp/dist/tools/messaging/log_action.js +22 -0
- package/framework-mcp/dist/tools/messaging/send_message.js +21 -0
- package/framework-mcp/dist/tools/types.js +1 -0
- package/framework-mcp/dist/utils/cli.js +20 -0
- package/framework-mcp/dist/utils/security.js +35 -0
- package/framework-mcp/package.json +1 -1
- package/framework-mcp/src/declarations.d.ts +17 -3
- package/framework-mcp/src/index.ts +50 -262
- package/framework-mcp/src/tools/definitions.ts +111 -0
- package/framework-mcp/src/tools/file_system/patch_file.ts +23 -0
- package/framework-mcp/src/tools/file_system/read_file.ts +9 -0
- package/framework-mcp/src/tools/file_system/replace_text.ts +18 -0
- package/framework-mcp/src/tools/file_system/write_file.ts +11 -0
- package/framework-mcp/src/tools/framework/get_status.ts +7 -0
- package/framework-mcp/src/tools/framework/orchestrate.ts +7 -0
- package/framework-mcp/src/tools/framework/update_contract_hash.ts +7 -0
- package/framework-mcp/src/tools/framework/update_memory.ts +10 -0
- package/framework-mcp/src/tools/index.ts +31 -0
- package/framework-mcp/src/tools/messaging/log_action.ts +28 -0
- package/framework-mcp/src/tools/messaging/send_message.ts +26 -0
- package/framework-mcp/src/tools/types.ts +40 -0
- package/framework-mcp/src/utils/cli.ts +20 -0
- package/framework-mcp/src/utils/security.ts +41 -0
- package/mcp.json +4 -2
- package/package.json +2 -2
- package/src/cli/adapters.ts +4 -1
- package/src/cli/utils/app.ts +1 -10
- package/src/cli/utils/claude.ts +8 -3
|
@@ -4,14 +4,17 @@ import {
|
|
|
4
4
|
CallToolRequestSchema,
|
|
5
5
|
ListToolsRequestSchema,
|
|
6
6
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
7
|
+
|
|
8
|
+
import { CallToolRequest, ToolResult } from "./tools/types.js";
|
|
9
|
+
import { TOOLS, toolHandlers } from "./tools/index.js";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
// ─── Server Setup ─────────────────────────────────────────────────
|
|
10
13
|
|
|
11
14
|
const server = new Server(
|
|
12
15
|
{
|
|
13
16
|
name: "agent-enderun-mcp",
|
|
14
|
-
version: "0.9.
|
|
17
|
+
version: "0.9.4",
|
|
15
18
|
},
|
|
16
19
|
{
|
|
17
20
|
capabilities: {
|
|
@@ -20,281 +23,66 @@ const server = new Server(
|
|
|
20
23
|
}
|
|
21
24
|
);
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
name: "read_file",
|
|
26
|
-
description: "Read the content of a file within the project.",
|
|
27
|
-
inputSchema: {
|
|
28
|
-
type: "object",
|
|
29
|
-
properties: {
|
|
30
|
-
path: { type: "string", description: "Path to the file relative to project root" },
|
|
31
|
-
},
|
|
32
|
-
required: ["path"],
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: "write_file",
|
|
37
|
-
description: "Write content to a file. Creates directories if missing.",
|
|
38
|
-
inputSchema: {
|
|
39
|
-
type: "object",
|
|
40
|
-
properties: {
|
|
41
|
-
path: { type: "string", description: "Path to the file relative to project root" },
|
|
42
|
-
content: { type: "string", description: "Complete content of the file" },
|
|
43
|
-
},
|
|
44
|
-
required: ["path", "content"],
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: "replace_text",
|
|
49
|
-
description: "Surgically replace a string in a file with another string.",
|
|
50
|
-
inputSchema: {
|
|
51
|
-
type: "object",
|
|
52
|
-
properties: {
|
|
53
|
-
path: { type: "string", description: "Path to the file" },
|
|
54
|
-
oldText: { type: "string", description: "The exact text to find" },
|
|
55
|
-
newText: { type: "string", description: "The text to replace it with" },
|
|
56
|
-
},
|
|
57
|
-
required: ["path", "oldText", "newText"],
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: "patch_file",
|
|
62
|
-
description: "Safely update a file by replacing a specific line range with new content.",
|
|
63
|
-
inputSchema: {
|
|
64
|
-
type: "object",
|
|
65
|
-
properties: {
|
|
66
|
-
path: { type: "string", description: "Path to the file" },
|
|
67
|
-
startLine: { type: "number", description: "Starting line number (1-indexed)" },
|
|
68
|
-
endLine: { type: "number", description: "Ending line number (inclusive)" },
|
|
69
|
-
newContent: { type: "string", description: "The new lines to insert" },
|
|
70
|
-
},
|
|
71
|
-
required: ["path", "startLine", "endLine", "newContent"],
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: "get_framework_status",
|
|
76
|
-
description: "Get the current project phase, active traces, and agent states.",
|
|
77
|
-
inputSchema: { type: "object", properties: {} },
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: "update_project_memory",
|
|
81
|
-
description: "Update a specific section in PROJECT_MEMORY.md.",
|
|
82
|
-
inputSchema: {
|
|
83
|
-
type: "object",
|
|
84
|
-
properties: {
|
|
85
|
-
section: { type: "string", description: "Section name (e.g., HISTORY, ACTIVE TASKS)" },
|
|
86
|
-
content: { type: "string", description: "Markdown content to append or set" },
|
|
87
|
-
},
|
|
88
|
-
required: ["section", "content"],
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "orchestrate_loop",
|
|
93
|
-
description: "Process the pending Hermes messages and trigger dynamic state transitions.",
|
|
94
|
-
inputSchema: { type: "object", properties: {} },
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: "send_agent_message",
|
|
98
|
-
description: "Send a Hermes protocol message to another agent.",
|
|
99
|
-
inputSchema: {
|
|
100
|
-
type: "object",
|
|
101
|
-
properties: {
|
|
102
|
-
to: { type: "string", description: "Target agent (e.g., @backend, @qa)" },
|
|
103
|
-
category: { type: "string", enum: ["ACTION", "DELEGATION", "INFO", "ALERT"] },
|
|
104
|
-
content: { type: "string", description: "Message content" },
|
|
105
|
-
traceId: { type: "string", description: "Active Trace ID" },
|
|
106
|
-
},
|
|
107
|
-
required: ["to", "category", "content", "traceId"],
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: "log_agent_action",
|
|
112
|
-
description: "Log an agent action to the framework logs.",
|
|
113
|
-
inputSchema: {
|
|
114
|
-
type: "object",
|
|
115
|
-
properties: {
|
|
116
|
-
agent: { type: "string", description: "The agent name (e.g., @manager, @backend)" },
|
|
117
|
-
action: { type: "string", description: "Action type or name" },
|
|
118
|
-
traceId: { type: "string", description: "The active Trace ID" },
|
|
119
|
-
status: { type: "string", enum: ["SUCCESS", "FAILURE"], description: "The status of the action" },
|
|
120
|
-
summary: { type: "string", description: "Brief description of the action taken" },
|
|
121
|
-
findings: { type: "array", items: { type: "string" }, description: "Optional findings or details" }
|
|
122
|
-
},
|
|
123
|
-
required: ["agent", "action", "traceId", "status", "summary"]
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
name: "update_contract_hash",
|
|
128
|
-
description: "Re-generate and synchronize the backend contract SHA-256 hash.",
|
|
129
|
-
inputSchema: { type: "object", properties: {} }
|
|
130
|
-
}
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
server.onRequest(ListToolsRequestSchema, async () => {
|
|
26
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
134
27
|
return { tools: TOOLS };
|
|
135
28
|
});
|
|
136
29
|
|
|
137
|
-
server.
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
const projectRoot = process.cwd();
|
|
30
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
31
|
+
const typedRequest = request as CallToolRequest;
|
|
32
|
+
const { name, arguments: args } = typedRequest.params;
|
|
33
|
+
const projectRoot = process.env.ENDERUN_PROJECT_ROOT || process.cwd();
|
|
141
34
|
|
|
142
35
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
case "write_file": {
|
|
151
|
-
const filePath = path.join(projectRoot, args?.path as string);
|
|
152
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
153
|
-
fs.writeFileSync(filePath, args?.content as string);
|
|
154
|
-
return { content: [{ type: "text", text: `✅ File written: ${args?.path}` }] };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
case "replace_text": {
|
|
158
|
-
const filePath = path.join(projectRoot, args?.path as string);
|
|
159
|
-
let content = fs.readFileSync(filePath, "utf8");
|
|
160
|
-
const oldText = args?.oldText as string;
|
|
161
|
-
const newText = args?.newText as string;
|
|
162
|
-
|
|
163
|
-
if (!content.includes(oldText)) {
|
|
164
|
-
throw new Error(`Text not found in file: ${oldText}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
content = content.replace(oldText, newText);
|
|
168
|
-
fs.writeFileSync(filePath, content);
|
|
169
|
-
return { content: [{ type: "text", text: `✅ Surgical edit successful in ${args?.path}` }] };
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
case "patch_file": {
|
|
173
|
-
const filePath = path.join(projectRoot, args?.path as string);
|
|
174
|
-
const lines = fs.readFileSync(filePath, "utf8").split("\n");
|
|
175
|
-
const start = (args?.startLine as number) - 1;
|
|
176
|
-
const end = (args?.endLine as number);
|
|
177
|
-
const newContent = (args?.newContent as string).split("\n");
|
|
178
|
-
|
|
179
|
-
if (start < 0 || start > lines.length) throw new Error("Invalid start line.");
|
|
180
|
-
|
|
181
|
-
lines.splice(start, end - start, ...newContent);
|
|
182
|
-
fs.writeFileSync(filePath, lines.join("\n"));
|
|
183
|
-
return { content: [{ type: "text", text: `✅ File patched successfully: ${args?.path}` }] };
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
case "get_framework_status": {
|
|
187
|
-
const output = execSync("npx agent-enderun status").toString();
|
|
188
|
-
return { content: [{ type: "text", text: output }] };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
case "update_project_memory": {
|
|
192
|
-
const section = args?.section as string;
|
|
193
|
-
const content = args?.content as string;
|
|
194
|
-
// Escape quotes for shell
|
|
195
|
-
const safeContent = content.replace(/"/g, "\\\"");
|
|
196
|
-
execSync(`npx agent-enderun update_project_memory "${section}" "${safeContent}"`);
|
|
197
|
-
return { content: [{ type: "text", text: `✅ Section ${section} updated.` }] };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
case "orchestrate_loop": {
|
|
201
|
-
const output = execSync("npx agent-enderun orchestrate").toString();
|
|
202
|
-
return { content: [{ type: "text", text: output }] };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
case "send_agent_message": {
|
|
206
|
-
interface SendAgentMessageArgs {
|
|
207
|
-
to: string;
|
|
208
|
-
category: "ACTION" | "DELEGATION" | "INFO" | "ALERT";
|
|
209
|
-
content: string;
|
|
210
|
-
traceId: string;
|
|
211
|
-
}
|
|
212
|
-
const { to, category, content, traceId } = args as any as SendAgentMessageArgs;
|
|
213
|
-
const frameworkDir = fs.existsSync(path.join(projectRoot, ".enderun")) ? ".enderun" : ".gemini";
|
|
214
|
-
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${to.replace("@", "")}.json`);
|
|
215
|
-
|
|
216
|
-
const message = {
|
|
217
|
-
timestamp: new Date().toISOString(),
|
|
218
|
-
from: "@mcp",
|
|
219
|
-
to,
|
|
220
|
-
category,
|
|
221
|
-
traceId,
|
|
222
|
-
content,
|
|
223
|
-
status: "PENDING"
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
fs.mkdirSync(path.dirname(messagePath), { recursive: true });
|
|
227
|
-
fs.appendFileSync(messagePath, JSON.stringify(message) + "\n");
|
|
228
|
-
|
|
229
|
-
return { content: [{ type: "text", text: `✅ Message sent to ${to}` }] };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
case "log_agent_action": {
|
|
233
|
-
interface LogAgentActionArgs {
|
|
234
|
-
agent: string;
|
|
235
|
-
action: string;
|
|
236
|
-
traceId: string;
|
|
237
|
-
status: "SUCCESS" | "FAILURE";
|
|
238
|
-
summary: string;
|
|
239
|
-
findings?: string[];
|
|
240
|
-
}
|
|
241
|
-
const { agent, action, traceId, status, summary, findings } = args as any as LogAgentActionArgs;
|
|
242
|
-
|
|
243
|
-
const candidates = [
|
|
244
|
-
".gemini/antigravity",
|
|
245
|
-
".gemini/antigravity-cli",
|
|
246
|
-
".gemini",
|
|
247
|
-
".claude",
|
|
248
|
-
".agent",
|
|
249
|
-
".enderun",
|
|
250
|
-
];
|
|
251
|
-
let frameworkDir = ".enderun";
|
|
252
|
-
for (const c of candidates) {
|
|
253
|
-
if (fs.existsSync(path.join(projectRoot, c))) {
|
|
254
|
-
frameworkDir = c;
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const agentName = agent.replace("@", "");
|
|
260
|
-
const logPath = path.join(projectRoot, frameworkDir, "logs", `${agentName}.json`);
|
|
261
|
-
|
|
262
|
-
const logEntry = {
|
|
263
|
-
timestamp: new Date().toISOString(),
|
|
264
|
-
agent,
|
|
265
|
-
action,
|
|
266
|
-
requestId: traceId,
|
|
267
|
-
status,
|
|
268
|
-
summary,
|
|
269
|
-
findings: findings || []
|
|
36
|
+
const handler = toolHandlers[name];
|
|
37
|
+
if (!handler) {
|
|
38
|
+
return {
|
|
39
|
+
isError: true,
|
|
40
|
+
content: [{ type: "text" as const, text: `Unknown tool: ${name}` }],
|
|
270
41
|
};
|
|
271
|
-
|
|
272
|
-
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
273
|
-
fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
|
|
274
|
-
|
|
275
|
-
return { content: [{ type: "text", text: `✅ Action logged for ${agent} to ${path.join(frameworkDir, "logs", `${agentName}.json`)}` }] };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
case "update_contract_hash": {
|
|
279
|
-
const output = execSync("npx agent-enderun update-contract").toString();
|
|
280
|
-
return { content: [{ type: "text", text: output }] };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
default:
|
|
284
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
285
42
|
}
|
|
43
|
+
return handler(projectRoot, args || {});
|
|
286
44
|
} catch (error: unknown) {
|
|
287
45
|
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
288
46
|
return {
|
|
289
47
|
isError: true,
|
|
290
|
-
content: [{ type: "text", text: message }],
|
|
48
|
+
content: [{ type: "text" as const, text: message }],
|
|
291
49
|
};
|
|
292
50
|
}
|
|
293
51
|
});
|
|
294
52
|
|
|
53
|
+
// ─── Graceful Startup & Shutdown ──────────────────────────────────
|
|
54
|
+
|
|
295
55
|
async function run() {
|
|
296
56
|
const transport = new StdioServerTransport();
|
|
57
|
+
|
|
58
|
+
// Prevent unhandled errors from crashing the MCP stream
|
|
59
|
+
process.on("uncaughtException", (error: Error) => {
|
|
60
|
+
process.stderr.write(`[agent-enderun-mcp] Uncaught exception: ${error.message}
|
|
61
|
+
`);
|
|
62
|
+
});
|
|
63
|
+
process.on("unhandledRejection", (reason: unknown) => {
|
|
64
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
65
|
+
process.stderr.write(`[agent-enderun-mcp] Unhandled rejection: ${message}
|
|
66
|
+
`);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Graceful shutdown on SIGINT/SIGTERM
|
|
70
|
+
const shutdown = async () => {
|
|
71
|
+
try {
|
|
72
|
+
await server.close();
|
|
73
|
+
} catch {
|
|
74
|
+
// Already closed or failed — safe to ignore
|
|
75
|
+
}
|
|
76
|
+
process.exit(0);
|
|
77
|
+
};
|
|
78
|
+
process.on("SIGINT", shutdown);
|
|
79
|
+
process.on("SIGTERM", shutdown);
|
|
80
|
+
|
|
297
81
|
await server.connect(transport);
|
|
298
82
|
}
|
|
299
83
|
|
|
300
|
-
run().catch(
|
|
84
|
+
run().catch((error: Error) => {
|
|
85
|
+
process.stderr.write(`[agent-enderun-mcp] Fatal startup error: ${error.message}
|
|
86
|
+
`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { ToolDefinition } from "../tools/types.js";
|
|
2
|
+
|
|
3
|
+
export const TOOLS: ToolDefinition[] = [
|
|
4
|
+
{
|
|
5
|
+
name: "read_file",
|
|
6
|
+
description: "Read the content of a file within the project.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
path: { type: "string", description: "Path to the file relative to project root" },
|
|
11
|
+
},
|
|
12
|
+
required: ["path"],
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "write_file",
|
|
17
|
+
description: "Write content to a file. Creates directories if missing.",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
path: { type: "string", description: "Path to the file relative to project root" },
|
|
22
|
+
content: { type: "string", description: "Complete content of the file" },
|
|
23
|
+
},
|
|
24
|
+
required: ["path", "content"],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "replace_text",
|
|
29
|
+
description: "Surgically replace a string in a file with another string.",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
path: { type: "string", description: "Path to the file" },
|
|
34
|
+
oldText: { type: "string", description: "The exact text to find" },
|
|
35
|
+
newText: { type: "string", description: "The text to replace it with" },
|
|
36
|
+
},
|
|
37
|
+
required: ["path", "oldText", "newText"],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "patch_file",
|
|
42
|
+
description: "Safely update a file by replacing a specific line range with new content.",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
path: { type: "string", description: "Path to the file" },
|
|
47
|
+
startLine: { type: "number", description: "Starting line number (1-indexed)" },
|
|
48
|
+
endLine: { type: "number", description: "Ending line number (inclusive)" },
|
|
49
|
+
newContent: { type: "string", description: "The new lines to insert" },
|
|
50
|
+
},
|
|
51
|
+
required: ["path", "startLine", "endLine", "newContent"],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "get_framework_status",
|
|
56
|
+
description: "Get the current project phase, active traces, and agent states.",
|
|
57
|
+
inputSchema: { type: "object", properties: {} },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "update_project_memory",
|
|
61
|
+
description: "Update a specific section in PROJECT_MEMORY.md.",
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
section: { type: "string", description: "Section name (e.g., HISTORY, ACTIVE TASKS)" },
|
|
66
|
+
content: { type: "string", description: "Markdown content to append or set" },
|
|
67
|
+
},
|
|
68
|
+
required: ["section", "content"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "orchestrate_loop",
|
|
73
|
+
description: "Process the pending Hermes messages and trigger dynamic state transitions.",
|
|
74
|
+
inputSchema: { type: "object", properties: {} },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "send_agent_message",
|
|
78
|
+
description: "Send a Hermes protocol message to another agent.",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
to: { type: "string", description: "Target agent (e.g., @backend, @qa)" },
|
|
83
|
+
category: { type: "string", enum: ["ACTION", "DELEGATION", "INFO", "ALERT"] },
|
|
84
|
+
content: { type: "string", description: "Message content" },
|
|
85
|
+
traceId: { type: "string", description: "Active Trace ID" },
|
|
86
|
+
},
|
|
87
|
+
required: ["to", "category", "content", "traceId"],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "log_agent_action",
|
|
92
|
+
description: "Log an agent action to the framework logs.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
agent: { type: "string", description: "The agent name (e.g., @manager, @backend)" },
|
|
97
|
+
action: { type: "string", description: "Action type or name" },
|
|
98
|
+
traceId: { type: "string", description: "The active Trace ID" },
|
|
99
|
+
status: { type: "string", enum: ["SUCCESS", "FAILURE"], description: "The status of the action" },
|
|
100
|
+
summary: { type: "string", description: "Brief description of the action taken" },
|
|
101
|
+
findings: { type: "string", description: "Optional comma-separated findings or details" }
|
|
102
|
+
},
|
|
103
|
+
required: ["agent", "action", "traceId", "status", "summary"]
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "update_contract_hash",
|
|
108
|
+
description: "Re-generate and synchronize the backend contract SHA-256 hash.",
|
|
109
|
+
inputSchema: { type: "object", properties: {} }
|
|
110
|
+
}
|
|
111
|
+
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { safePath } from "../../utils/security.js";
|
|
3
|
+
import { ToolArgs, ToolResult } from "../types.js";
|
|
4
|
+
|
|
5
|
+
export function handlePatchFile(projectRoot: string, args: ToolArgs): ToolResult {
|
|
6
|
+
const filePath = safePath(projectRoot, args.path as string);
|
|
7
|
+
const lines = fs.readFileSync(filePath, "utf8").split("\n");
|
|
8
|
+
const start = (args.startLine as number) - 1;
|
|
9
|
+
const end = args.endLine as number;
|
|
10
|
+
const newContent = (args.newContent as string).split("\n");
|
|
11
|
+
|
|
12
|
+
if (start < 0 || start > lines.length) {
|
|
13
|
+
throw new Error(`Invalid start line: ${start + 1}. File has ${lines.length} lines.`);
|
|
14
|
+
}
|
|
15
|
+
if (end < start + 1 || end > lines.length) {
|
|
16
|
+
throw new Error(`Invalid end line: ${end}. Must be between ${start + 1} and ${lines.length}.`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
lines.splice(start, end - start, ...newContent);
|
|
20
|
+
fs.writeFileSync(filePath, lines.join("\n"));
|
|
21
|
+
return { content: [{ type: "text", text: `✅ File patched successfully: ${args.path}` }] };
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { safePath } from "../../utils/security.js";
|
|
3
|
+
import { ToolArgs, ToolResult } from "../types.js";
|
|
4
|
+
|
|
5
|
+
export function handleReadFile(projectRoot: string, args: ToolArgs): ToolResult {
|
|
6
|
+
const filePath = safePath(projectRoot, args.path as string);
|
|
7
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
8
|
+
return { content: [{ type: "text", text: content }] };
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { safePath } from "../../utils/security.js";
|
|
3
|
+
import { ToolArgs, ToolResult } from "../types.js";
|
|
4
|
+
|
|
5
|
+
export function handleReplaceText(projectRoot: string, args: ToolArgs): ToolResult {
|
|
6
|
+
const filePath = safePath(projectRoot, args.path as string);
|
|
7
|
+
let content = fs.readFileSync(filePath, "utf8");
|
|
8
|
+
const oldText = args.oldText as string;
|
|
9
|
+
const newText = args.newText as string;
|
|
10
|
+
|
|
11
|
+
if (!content.includes(oldText)) {
|
|
12
|
+
throw new Error(`Text not found in file: ${oldText.slice(0, 100)}...`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
content = content.replace(oldText, newText);
|
|
16
|
+
fs.writeFileSync(filePath, content);
|
|
17
|
+
return { content: [{ type: "text", text: `✅ Surgical edit successful in ${args.path}` }] };
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { safePath } from "../../utils/security.js";
|
|
4
|
+
import { ToolArgs, ToolResult } from "../types.js";
|
|
5
|
+
|
|
6
|
+
export function handleWriteFile(projectRoot: string, args: ToolArgs): ToolResult {
|
|
7
|
+
const filePath = safePath(projectRoot, args.path as string);
|
|
8
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
9
|
+
fs.writeFileSync(filePath, args.content as string);
|
|
10
|
+
return { content: [{ type: "text", text: `✅ File written: ${args.path}` }] };
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { safeExec } from "../../utils/cli.js";
|
|
2
|
+
import { ToolResult } from "../types.js";
|
|
3
|
+
|
|
4
|
+
export function handleGetFrameworkStatus(projectRoot: string): ToolResult {
|
|
5
|
+
const output = safeExec("npx", ["agent-enderun", "status"], projectRoot);
|
|
6
|
+
return { content: [{ type: "text", text: output }] };
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { safeExec } from "../../utils/cli.js";
|
|
2
|
+
import { ToolResult } from "../types.js";
|
|
3
|
+
|
|
4
|
+
export function handleOrchestrateLoop(projectRoot: string): ToolResult {
|
|
5
|
+
const output = safeExec("npx", ["agent-enderun", "orchestrate"], projectRoot);
|
|
6
|
+
return { content: [{ type: "text", text: output }] };
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { safeExec } from "../../utils/cli.js";
|
|
2
|
+
import { ToolResult } from "../types.js";
|
|
3
|
+
|
|
4
|
+
export function handleUpdateContractHash(projectRoot: string): ToolResult {
|
|
5
|
+
const output = safeExec("npx", ["agent-enderun", "update-contract"], projectRoot);
|
|
6
|
+
return { content: [{ type: "text", text: output }] };
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { safeExec } from "../../utils/cli.js";
|
|
2
|
+
import { ToolArgs, ToolResult } from "../types.js";
|
|
3
|
+
|
|
4
|
+
export function handleUpdateProjectMemory(projectRoot: string, args: ToolArgs): ToolResult {
|
|
5
|
+
const section = args.section as string;
|
|
6
|
+
const content = args.content as string;
|
|
7
|
+
// Using execFileSync with array args prevents command injection
|
|
8
|
+
safeExec("npx", ["agent-enderun", "update_project_memory", section, content], projectRoot);
|
|
9
|
+
return { content: [{ type: "text", text: `✅ Section ${section} updated.` }] };
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { TOOLS } from "./definitions.js";
|
|
2
|
+
import { handleReadFile } from "./file_system/read_file.js";
|
|
3
|
+
import { handleWriteFile } from "./file_system/write_file.js";
|
|
4
|
+
import { handleReplaceText } from "./file_system/replace_text.js";
|
|
5
|
+
import { handlePatchFile } from "./file_system/patch_file.js";
|
|
6
|
+
import { handleGetFrameworkStatus } from "./framework/get_status.js";
|
|
7
|
+
import { handleUpdateProjectMemory } from "./framework/update_memory.js";
|
|
8
|
+
import { handleOrchestrateLoop } from "./framework/orchestrate.js";
|
|
9
|
+
import { handleUpdateContractHash } from "./framework/update_contract_hash.js";
|
|
10
|
+
import { handleSendAgentMessage } from "./messaging/send_message.js";
|
|
11
|
+
import { handleLogAgentAction } from "./messaging/log_action.js";
|
|
12
|
+
import { ToolResult, ToolArgs } from "./types.js";
|
|
13
|
+
|
|
14
|
+
// Define a type for the tool handlers
|
|
15
|
+
export type ToolHandler = (projectRoot: string, args: ToolArgs) => ToolResult;
|
|
16
|
+
|
|
17
|
+
// Map of tool names to their handler functions
|
|
18
|
+
export const toolHandlers: Record<string, ToolHandler> = {
|
|
19
|
+
read_file: handleReadFile,
|
|
20
|
+
write_file: handleWriteFile,
|
|
21
|
+
replace_text: handleReplaceText,
|
|
22
|
+
patch_file: handlePatchFile,
|
|
23
|
+
get_framework_status: handleGetFrameworkStatus,
|
|
24
|
+
update_project_memory: handleUpdateProjectMemory,
|
|
25
|
+
orchestrate_loop: handleOrchestrateLoop,
|
|
26
|
+
send_agent_message: handleSendAgentMessage,
|
|
27
|
+
log_agent_action: handleLogAgentAction,
|
|
28
|
+
update_contract_hash: handleUpdateContractHash,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { TOOLS };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { resolveFrameworkDir } from "../../utils/security.js";
|
|
4
|
+
import { ToolArgs, ToolResult } from "../types.js";
|
|
5
|
+
|
|
6
|
+
export function handleLogAgentAction(projectRoot: string, args: ToolArgs): ToolResult {
|
|
7
|
+
const { agent, action, traceId, status, summary } = args as Required<Pick<ToolArgs, "agent" | "action" | "traceId" | "status" | "summary">>;
|
|
8
|
+
const findings = args.findings ? args.findings.split(",").map(f => f.trim()) : [];
|
|
9
|
+
|
|
10
|
+
const frameworkDir = resolveFrameworkDir(projectRoot);
|
|
11
|
+
const agentName = (agent as string).replace("@", "");
|
|
12
|
+
const logPath = path.join(projectRoot, frameworkDir, "logs", `${agentName}.json`);
|
|
13
|
+
|
|
14
|
+
const logEntry = {
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
agent,
|
|
17
|
+
action,
|
|
18
|
+
requestId: traceId,
|
|
19
|
+
status,
|
|
20
|
+
summary,
|
|
21
|
+
findings
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
25
|
+
fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
|
|
26
|
+
|
|
27
|
+
return { content: [{ type: "text", text: `✅ Action logged for ${agent} to ${path.join(frameworkDir, "logs", `${agentName}.json`)}` }] };
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { resolveFrameworkDir } from "../../utils/security.js";
|
|
4
|
+
import { ToolArgs, ToolResult } from "../types.js";
|
|
5
|
+
|
|
6
|
+
export function handleSendAgentMessage(projectRoot: string, args: ToolArgs): ToolResult {
|
|
7
|
+
const { to, category, content, traceId } = args as Required<Pick<ToolArgs, "to" | "category" | "content" | "traceId">>;
|
|
8
|
+
const frameworkDir = resolveFrameworkDir(projectRoot);
|
|
9
|
+
const agentName = to.replace("@", "");
|
|
10
|
+
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
11
|
+
|
|
12
|
+
const message = {
|
|
13
|
+
timestamp: new Date().toISOString(),
|
|
14
|
+
from: "@mcp",
|
|
15
|
+
to,
|
|
16
|
+
category,
|
|
17
|
+
traceId,
|
|
18
|
+
content,
|
|
19
|
+
status: "PENDING"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
fs.mkdirSync(path.dirname(messagePath), { recursive: true });
|
|
23
|
+
fs.appendFileSync(messagePath, JSON.stringify(message) + "\n");
|
|
24
|
+
|
|
25
|
+
return { content: [{ type: "text", text: `✅ Message sent to ${to}` }] };
|
|
26
|
+
}
|