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