agent-enderun 0.8.8 → 0.9.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/BRAIN_DASHBOARD.md +4 -4
- package/{ENDERUN.md → .enderun/ENDERUN.md} +4 -2
- package/.enderun/PROJECT_MEMORY.md +12 -45
- package/.enderun/STATUS.md +9 -8
- package/.enderun/agents/agent_army_schema.json +29 -7
- package/.enderun/agents/analyst.md +3 -5
- package/.enderun/agents/backend.md +11 -31
- package/.enderun/agents/devops.md +2 -3
- package/.enderun/agents/explorer.md +4 -4
- package/.enderun/agents/frontend.md +76 -69
- package/.enderun/agents/git.md +3 -3
- package/.enderun/agents/manager.md +30 -42
- package/.enderun/agents/mobile.md +3 -3
- package/.enderun/agents/native.md +106 -0
- package/.enderun/agents/orchestrator.md +2 -2
- package/.enderun/agents/qa.md +7 -10
- package/.enderun/agents/schema/agent-lifecycle-schema.json +29 -8
- package/.enderun/agents/security.md +2 -2
- package/.enderun/cli-commands.json +4 -0
- package/.enderun/config.json +2 -2
- package/.enderun/knowledge/SHIM_TEMPLATE.md +25 -0
- package/.enderun/knowledge/eslint-standards.md +63 -0
- package/.enderun/knowledge/hermes_protocol.md +1 -1
- package/.enderun/knowledge/manager_authority_audit_enforcement.md +2 -2
- package/.enderun/mcp_config.json +9 -0
- package/.enderun/memory-graph/agent-contexts/analyst.json +1 -1
- 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 -0
- package/.enderun/memory-graph/agent-contexts/orchestrator.json +1 -1
- package/.enderun/memory-graph/agent-contexts/qa.json +1 -1
- package/.enderun/memory-graph/agent-contexts/security.json +1 -1
- package/.enderun/memory-graph/graph.json +123 -15
- package/.enderun/memory-graph/shared-facts.json +26 -31
- package/README.md +89 -413
- package/agent.md +25 -0
- package/bin/cli.js +9 -9
- package/bin/update-contract.js +34 -37
- package/bin/validate-agent-army.js +38 -13
- package/claude.md +14 -8
- package/docs/README.md +35 -16
- package/docs/tech-stack.md +30 -0
- package/eslint.config.js +68 -0
- package/framework-mcp/dist/index.js +184 -24
- package/framework-mcp/package.json +6 -33
- package/framework-mcp/tsconfig.json +3 -6
- package/gemini.md +12 -6
- package/grok.md +25 -0
- package/package.json +12 -6
- package/panda.config.ts +86 -86
- package/tsconfig.json +9 -0
- package/.enderun/logs/analyst.json +0 -1
- package/.enderun/logs/backend.json +0 -1
- package/.enderun/logs/devops.json +0 -1
- package/.enderun/logs/explorer.json +0 -1
- package/.enderun/logs/frontend.json +0 -1
- package/.enderun/logs/git.json +0 -1
- package/.enderun/logs/manager.json +0 -1
- package/.enderun/logs/mobile.json +0 -1
- package/.enderun/logs/native.json +0 -1
- package/.enderun/logs/orchestrator.json +0 -1
- package/.enderun/logs/qa.json +0 -1
- package/.enderun/logs/security.json +0 -1
- package/.env.example +0 -10
- package/cursor.md +0 -19
- package/framework-mcp/README.md +0 -55
- package/framework-mcp/dist/schemas.js +0 -84
- package/framework-mcp/dist/tools/codebase.js +0 -294
- package/framework-mcp/dist/tools/contract.js +0 -197
- package/framework-mcp/dist/tools/framework.js +0 -225
- package/framework-mcp/dist/tools/git.js +0 -51
- package/framework-mcp/dist/tools/governance.js +0 -696
- package/framework-mcp/dist/tools/index.js +0 -47
- package/framework-mcp/dist/tools/knowledge.js +0 -133
- package/framework-mcp/dist/tools/memory.js +0 -217
- package/framework-mcp/dist/tools/messages.js +0 -193
- package/framework-mcp/dist/tools/monitoring.js +0 -294
- package/framework-mcp/dist/tools/orchestration.js +0 -369
- package/framework-mcp/dist/tools/pipeline.js +0 -267
- package/framework-mcp/dist/tools/security.js +0 -137
- package/framework-mcp/dist/utils.js +0 -121
- package/framework-mcp/src/index.ts +0 -49
- package/framework-mcp/src/schemas.ts +0 -106
- package/framework-mcp/src/tools/codebase.ts +0 -284
- package/framework-mcp/src/tools/contract.ts +0 -221
- package/framework-mcp/src/tools/framework.ts +0 -223
- package/framework-mcp/src/tools/git.ts +0 -42
- package/framework-mcp/src/tools/governance.ts +0 -891
- package/framework-mcp/src/tools/index.ts +0 -50
- package/framework-mcp/src/tools/knowledge.ts +0 -141
- package/framework-mcp/src/tools/memory.ts +0 -207
- package/framework-mcp/src/tools/messages.ts +0 -213
- package/framework-mcp/src/tools/monitoring.ts +0 -351
- package/framework-mcp/src/tools/orchestration.ts +0 -440
- package/framework-mcp/src/tools/pipeline.ts +0 -353
- package/framework-mcp/src/tools/security.ts +0 -143
- package/framework-mcp/src/utils.ts +0 -130
- package/src/cli/index.ts +0 -2188
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { codebaseTools, codebaseHandlers } from "./codebase.js";
|
|
2
|
-
import { frameworkTools, frameworkHandlers } from "./framework.js";
|
|
3
|
-
import { securityTools, securityHandlers } from "./security.js";
|
|
4
|
-
import { memoryTools, memoryHandlers } from "./memory.js";
|
|
5
|
-
import { contractTools, contractHandlers } from "./contract.js";
|
|
6
|
-
import { messageTools, messageHandlers } from "./messages.js";
|
|
7
|
-
import { gitTools, gitHandlers } from "./git.js";
|
|
8
|
-
import { knowledgeTools, knowledgeHandlers } from "./knowledge.js";
|
|
9
|
-
import { governanceTools, governanceHandlers } from "./governance.js";
|
|
10
|
-
import { orchestrationTools, orchestrationHandlers } from "./orchestration.js";
|
|
11
|
-
import { monitoringTools, monitoringHandlers } from "./monitoring.js";
|
|
12
|
-
import { pipelineTools, pipelineHandlers } from "./pipeline.js";
|
|
13
|
-
export const allTools = [
|
|
14
|
-
...codebaseTools,
|
|
15
|
-
...frameworkTools,
|
|
16
|
-
...securityTools,
|
|
17
|
-
...memoryTools,
|
|
18
|
-
...contractTools,
|
|
19
|
-
...messageTools,
|
|
20
|
-
...gitTools,
|
|
21
|
-
...knowledgeTools,
|
|
22
|
-
...governanceTools,
|
|
23
|
-
// v1.0 Army Core
|
|
24
|
-
...orchestrationTools,
|
|
25
|
-
...monitoringTools,
|
|
26
|
-
...pipelineTools,
|
|
27
|
-
];
|
|
28
|
-
export const allHandlers = {
|
|
29
|
-
...codebaseHandlers,
|
|
30
|
-
...frameworkHandlers,
|
|
31
|
-
...securityHandlers,
|
|
32
|
-
...memoryHandlers,
|
|
33
|
-
...contractHandlers,
|
|
34
|
-
...messageHandlers,
|
|
35
|
-
...gitHandlers,
|
|
36
|
-
...knowledgeHandlers,
|
|
37
|
-
...governanceHandlers,
|
|
38
|
-
// v1.0 Army Core
|
|
39
|
-
...orchestrationHandlers,
|
|
40
|
-
...monitoringHandlers,
|
|
41
|
-
...pipelineHandlers,
|
|
42
|
-
};
|
|
43
|
-
// Add compatibility aliases
|
|
44
|
-
allHandlers.codebase_search = allHandlers.search_codebase;
|
|
45
|
-
allHandlers.codebase_graph_query = allHandlers.analyze_dependencies;
|
|
46
|
-
allHandlers.codebase_status = allHandlers.get_framework_status;
|
|
47
|
-
allHandlers.codebase_context_search = allHandlers.search_codebase; // Modified in original but search_codebase handles extension
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { getFrameworkDir } from "../utils.js";
|
|
4
|
-
import { SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA, UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA } from "../schemas.js";
|
|
5
|
-
export const knowledgeTools = [
|
|
6
|
-
{
|
|
7
|
-
name: "search_knowledge_base",
|
|
8
|
-
description: "Searches the Academy's Obsidian-style knowledge base using keywords or tags.",
|
|
9
|
-
inputSchema: {
|
|
10
|
-
type: "object",
|
|
11
|
-
properties: {
|
|
12
|
-
query: { type: "string", description: "Search query, topic, or tag (e.g. #security)" },
|
|
13
|
-
},
|
|
14
|
-
required: ["query"],
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: "update_knowledge_base",
|
|
19
|
-
description: "Adds or updates an entry with mandatory YAML frontmatter for Obsidian compatibility.",
|
|
20
|
-
inputSchema: {
|
|
21
|
-
type: "object",
|
|
22
|
-
properties: {
|
|
23
|
-
topic: { type: "string", description: "The topic or title" },
|
|
24
|
-
content: { type: "string", description: "Content (YAML frontmatter will be auto-generated if missing)" },
|
|
25
|
-
tags: { type: "array", items: { type: "string" }, description: "Tags for the entry" },
|
|
26
|
-
},
|
|
27
|
-
required: ["topic", "content"],
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
name: "get_knowledge_graph",
|
|
32
|
-
description: "Generates a Mermaid diagram showing relationships between knowledge entries based on 'related' metadata.",
|
|
33
|
-
inputSchema: {
|
|
34
|
-
type: "object",
|
|
35
|
-
properties: {
|
|
36
|
-
tag: { type: "string", description: "Filter by tag" },
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
];
|
|
41
|
-
function parseFrontmatter(content) {
|
|
42
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
43
|
-
if (!match)
|
|
44
|
-
return { metadata: {}, body: content };
|
|
45
|
-
const yaml = match[1];
|
|
46
|
-
const metadata = {};
|
|
47
|
-
yaml.split("\n").forEach(line => {
|
|
48
|
-
const [key, ...val] = line.split(":");
|
|
49
|
-
if (key && val.length > 0)
|
|
50
|
-
metadata[key.trim()] = val.join(":").trim();
|
|
51
|
-
});
|
|
52
|
-
return { metadata, body: content.replace(match[0], "").trim() };
|
|
53
|
-
}
|
|
54
|
-
export const knowledgeHandlers = {
|
|
55
|
-
search_knowledge_base: async (args, projectRoot) => {
|
|
56
|
-
const parsed = SEARCH_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
57
|
-
if (!parsed.success)
|
|
58
|
-
return { content: [{ type: "text", text: "Invalid search query." }] };
|
|
59
|
-
try {
|
|
60
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
61
|
-
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
62
|
-
if (!fs.existsSync(kbDir))
|
|
63
|
-
return { content: [{ type: "text", text: "Knowledge base is empty." }] };
|
|
64
|
-
const query = parsed.data.query.toLowerCase();
|
|
65
|
-
const results = fs.readdirSync(kbDir).filter(f => f.endsWith(".md")).map(file => {
|
|
66
|
-
const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
|
|
67
|
-
const { metadata, body } = parseFrontmatter(content);
|
|
68
|
-
const matchesQuery = body.toLowerCase().includes(query) ||
|
|
69
|
-
file.toLowerCase().includes(query) ||
|
|
70
|
-
(metadata.tags && metadata.tags.toLowerCase().includes(query)) ||
|
|
71
|
-
(metadata.title && metadata.title.toLowerCase().includes(query));
|
|
72
|
-
if (matchesQuery) {
|
|
73
|
-
return `### ${metadata.title || file.replace(".md", "")}\n**Tags:** ${metadata.tags || "None"}\n\n${body.slice(0, 300)}...`;
|
|
74
|
-
}
|
|
75
|
-
return null;
|
|
76
|
-
}).filter(Boolean);
|
|
77
|
-
return { content: [{ type: "text", text: results.length > 0 ? `### KNOWLEDGE BASE SEARCH RESULTS\n\n${results.join("\n\n---\n\n")}` : "No matching knowledge entries found." }] };
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return { content: [{ type: "text", text: "Knowledge base search failed." }] };
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
update_knowledge_base: async (args, projectRoot) => {
|
|
84
|
-
const parsed = UPDATE_KNOWLEDGE_BASE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
85
|
-
if (!parsed.success)
|
|
86
|
-
return { content: [{ type: "text", text: "Invalid arguments." }] };
|
|
87
|
-
try {
|
|
88
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
89
|
-
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
90
|
-
if (!fs.existsSync(kbDir))
|
|
91
|
-
fs.mkdirSync(kbDir, { recursive: true });
|
|
92
|
-
const fileName = parsed.data.topic.replace(/[^a-z0-9]/gi, "_").toLowerCase() + ".md";
|
|
93
|
-
const tags = parsed.data.tags || [];
|
|
94
|
-
let finalContent = parsed.data.content;
|
|
95
|
-
if (!finalContent.startsWith("---")) {
|
|
96
|
-
const frontmatter = `---\ntitle: ${parsed.data.topic}\ntags: [${tags.join(", ")}]\nlast_updated: ${new Date().toISOString()}\n---\n\n`;
|
|
97
|
-
finalContent = frontmatter + finalContent;
|
|
98
|
-
}
|
|
99
|
-
fs.writeFileSync(path.join(kbDir, fileName), finalContent);
|
|
100
|
-
return { content: [{ type: "text", text: `Obsidian Wiki updated: ${parsed.data.topic}` }] };
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return { content: [{ type: "text", text: "Failed to update knowledge base." }] };
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
get_knowledge_graph: async (args, projectRoot) => {
|
|
107
|
-
try {
|
|
108
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
109
|
-
const kbDir = path.join(projectRoot, frameworkDir, "knowledge");
|
|
110
|
-
if (!fs.existsSync(kbDir))
|
|
111
|
-
return { content: [{ type: "text", text: "Knowledge base empty." }] };
|
|
112
|
-
const files = fs.readdirSync(kbDir).filter(f => f.endsWith(".md"));
|
|
113
|
-
let mermaid = "graph TD\n";
|
|
114
|
-
files.forEach(file => {
|
|
115
|
-
const content = fs.readFileSync(path.join(kbDir, file), "utf-8");
|
|
116
|
-
const { metadata } = parseFrontmatter(content);
|
|
117
|
-
const id = file.replace(".md", "");
|
|
118
|
-
const label = metadata.title || id;
|
|
119
|
-
mermaid += ` ${id}["${label}"]\n`;
|
|
120
|
-
if (metadata.related) {
|
|
121
|
-
const related = metadata.related.replace(/[[\]]/g, "").split(",");
|
|
122
|
-
related.forEach((r) => {
|
|
123
|
-
mermaid += ` ${id} --> ${r.trim().replace(".md", "")}\n`;
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
return { content: [{ type: "text", text: `### KNOWLEDGE GRAPH (Mermaid)\n\n\`\`\`mermaid\n${mermaid}\n\`\`\`` }] };
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
return { content: [{ type: "text", text: "Failed to get knowledge graph." }] };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
};
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { getFrameworkDir, prependToSection, replaceSectionContent } from "../utils.js";
|
|
4
|
-
import { UPDATE_MEMORY_ARGS_SCHEMA } from "../schemas.js";
|
|
5
|
-
export const memoryTools = [
|
|
6
|
-
{
|
|
7
|
-
name: "update_project_memory",
|
|
8
|
-
description: "Update a specific section of PROJECT_MEMORY.md (CURRENT STATUS, HISTORY, or ACTIVE TASKS) with new content.",
|
|
9
|
-
inputSchema: {
|
|
10
|
-
type: "object",
|
|
11
|
-
properties: {
|
|
12
|
-
section: {
|
|
13
|
-
type: "string",
|
|
14
|
-
enum: ["CURRENT STATUS", "HISTORY", "ACTIVE TASKS"],
|
|
15
|
-
description: "The section to update.",
|
|
16
|
-
},
|
|
17
|
-
content: {
|
|
18
|
-
type: "string",
|
|
19
|
-
description: "The new content to append or replace in that section.",
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
required: ["section", "content"],
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
name: "read_project_memory",
|
|
27
|
-
description: "Read the entire content of PROJECT_MEMORY.md safely. Use this instead of direct ReadFile tools to ensure framework compatibility.",
|
|
28
|
-
inputSchema: { type: "object", properties: {} },
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
name: "get_army_graph",
|
|
32
|
-
description: "Read the current Agent Army interaction graph (memory-graph/graph.json). Useful for @orchestrator and @manager to see relationships and health.",
|
|
33
|
-
inputSchema: { type: "object", properties: {} },
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: "get_agent_context",
|
|
37
|
-
description: "Read the private persistent context for a specific agent from memory-graph/agent-contexts/{agent}.json (without the @ prefix).",
|
|
38
|
-
inputSchema: {
|
|
39
|
-
type: "object",
|
|
40
|
-
properties: {
|
|
41
|
-
agent: { type: "string", description: "Agent name without @ (e.g. manager, orchestrator, backend)" }
|
|
42
|
-
},
|
|
43
|
-
required: ["agent"]
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
name: "update_shared_facts",
|
|
48
|
-
description: "Update the shared facts file used by the entire Agent Army (memory-graph/shared-facts.json).",
|
|
49
|
-
inputSchema: {
|
|
50
|
-
type: "object",
|
|
51
|
-
properties: {
|
|
52
|
-
facts: { type: "object", description: "Partial or full facts object to merge into shared-facts.json" }
|
|
53
|
-
},
|
|
54
|
-
required: ["facts"]
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: "update_agent_context",
|
|
59
|
-
description: "Update the private context for a specific agent in memory-graph/agent-contexts/.",
|
|
60
|
-
inputSchema: {
|
|
61
|
-
type: "object",
|
|
62
|
-
properties: {
|
|
63
|
-
agent: { type: "string", description: "Agent name without @" },
|
|
64
|
-
context: { type: "object", description: "Data to write/merge for this agent" }
|
|
65
|
-
},
|
|
66
|
-
required: ["agent", "context"]
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
];
|
|
70
|
-
export const memoryHandlers = {
|
|
71
|
-
update_project_memory: async (args, projectRoot) => {
|
|
72
|
-
const parsed = UPDATE_MEMORY_ARGS_SCHEMA.safeParse(args ?? {});
|
|
73
|
-
if (!parsed.success)
|
|
74
|
-
return { content: [{ type: "text", text: "Invalid section or content." }] };
|
|
75
|
-
const { section, content } = parsed.data;
|
|
76
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
77
|
-
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
78
|
-
const lockPath = memoryPath + ".lock";
|
|
79
|
-
const lockOwner = `lock-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
80
|
-
try {
|
|
81
|
-
try {
|
|
82
|
-
// Atomic write using 'wx' flag to prevent TOCTOU race conditions
|
|
83
|
-
fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }), { flag: "wx" });
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
if (error.code === "EEXIST") {
|
|
87
|
-
const stats = fs.statSync(lockPath);
|
|
88
|
-
if (Date.now() - stats.mtimeMs > 120000) {
|
|
89
|
-
try {
|
|
90
|
-
fs.unlinkSync(lockPath);
|
|
91
|
-
// Retry atomic write once after unlinking stale lock
|
|
92
|
-
fs.writeFileSync(lockPath, JSON.stringify({ owner: lockOwner, createdAt: new Date().toISOString() }), { flag: "wx" });
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
return { content: [{ type: "text", text: "Memory is locked. Try again later." }] };
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
let memoryContent = fs.readFileSync(memoryPath, "utf-8");
|
|
107
|
-
if (section === "HISTORY") {
|
|
108
|
-
let updated = false;
|
|
109
|
-
const headers = ["HISTORY (Persistent Memory)", "HISTORY"];
|
|
110
|
-
for (const h of headers) {
|
|
111
|
-
try {
|
|
112
|
-
memoryContent = prependToSection(memoryContent, h, content);
|
|
113
|
-
updated = true;
|
|
114
|
-
break;
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Ignore section not found
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (!updated)
|
|
121
|
-
throw new Error("HISTORY section not found.");
|
|
122
|
-
}
|
|
123
|
-
else if (section === "CURRENT STATUS") {
|
|
124
|
-
memoryContent = replaceSectionContent(memoryContent, "CURRENT STATUS", content);
|
|
125
|
-
}
|
|
126
|
-
else if (section === "ACTIVE TASKS") {
|
|
127
|
-
memoryContent = replaceSectionContent(memoryContent, "ACTIVE TASKS", content);
|
|
128
|
-
}
|
|
129
|
-
fs.writeFileSync(memoryPath, memoryContent);
|
|
130
|
-
if (fs.existsSync(lockPath) && fs.readFileSync(lockPath, "utf-8").includes(lockOwner))
|
|
131
|
-
fs.unlinkSync(lockPath);
|
|
132
|
-
return { content: [{ type: "text", text: `Section ${section} updated successfully.` }] };
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
return { content: [{ type: "text", text: "Failed to update project memory." }] };
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
read_project_memory: async (args, projectRoot) => {
|
|
139
|
-
try {
|
|
140
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
141
|
-
const memoryPath = path.join(projectRoot, frameworkDir, "PROJECT_MEMORY.md");
|
|
142
|
-
if (!fs.existsSync(memoryPath))
|
|
143
|
-
return { content: [{ type: "text", text: `ERROR: ${frameworkDir}/PROJECT_MEMORY.md not found.` }] };
|
|
144
|
-
return { content: [{ type: "text", text: fs.readFileSync(memoryPath, "utf-8") }] };
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
return { content: [{ type: "text", text: "Failed to read project memory." }] };
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
get_army_graph: async (args, projectRoot) => {
|
|
151
|
-
try {
|
|
152
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
153
|
-
const graphPath = path.join(projectRoot, frameworkDir, "memory-graph", "graph.json");
|
|
154
|
-
if (!fs.existsSync(graphPath))
|
|
155
|
-
return { content: [{ type: "text", text: "Army graph not found. Run army initialization first." }] };
|
|
156
|
-
const graph = JSON.parse(fs.readFileSync(graphPath, "utf-8"));
|
|
157
|
-
return { content: [{ type: "text", text: JSON.stringify(graph, null, 2) }] };
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
return { content: [{ type: "text", text: "Failed to read army graph." }] };
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
get_agent_context: async (args, projectRoot) => {
|
|
164
|
-
try {
|
|
165
|
-
const { agent } = args;
|
|
166
|
-
if (!agent)
|
|
167
|
-
return { content: [{ type: "text", text: "Agent name required." }] };
|
|
168
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
169
|
-
const contextPath = path.join(projectRoot, frameworkDir, "memory-graph", "agent-contexts", `${agent.replace(/^@/, "")}.json`);
|
|
170
|
-
if (!fs.existsSync(contextPath))
|
|
171
|
-
return { content: [{ type: "text", text: `No private context found for @${agent}.` }] };
|
|
172
|
-
const ctx = JSON.parse(fs.readFileSync(contextPath, "utf-8"));
|
|
173
|
-
return { content: [{ type: "text", text: JSON.stringify(ctx, null, 2) }] };
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
return { content: [{ type: "text", text: "Failed to read agent context." }] };
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
update_shared_facts: async (args, projectRoot) => {
|
|
180
|
-
try {
|
|
181
|
-
const { facts } = args;
|
|
182
|
-
if (!facts)
|
|
183
|
-
return { content: [{ type: "text", text: "facts object required." }] };
|
|
184
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
185
|
-
const factsPath = path.join(projectRoot, frameworkDir, "memory-graph", "shared-facts.json");
|
|
186
|
-
let existing = {};
|
|
187
|
-
if (fs.existsSync(factsPath)) {
|
|
188
|
-
existing = JSON.parse(fs.readFileSync(factsPath, "utf-8"));
|
|
189
|
-
}
|
|
190
|
-
const merged = { ...existing, ...facts, updatedAt: new Date().toISOString() };
|
|
191
|
-
fs.writeFileSync(factsPath, JSON.stringify(merged, null, 2));
|
|
192
|
-
return { content: [{ type: "text", text: "Shared facts updated successfully." }] };
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
return { content: [{ type: "text", text: "Failed to update shared facts." }] };
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
update_agent_context: async (args, projectRoot) => {
|
|
199
|
-
try {
|
|
200
|
-
const { agent, context } = args;
|
|
201
|
-
if (!agent || !context)
|
|
202
|
-
return { content: [{ type: "text", text: "agent and context required." }] };
|
|
203
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
204
|
-
const ctxPath = path.join(projectRoot, frameworkDir, "memory-graph", "agent-contexts", `${agent.replace(/^@/, "")}.json`);
|
|
205
|
-
let existing = {};
|
|
206
|
-
if (fs.existsSync(ctxPath)) {
|
|
207
|
-
existing = JSON.parse(fs.readFileSync(ctxPath, "utf-8"));
|
|
208
|
-
}
|
|
209
|
-
const merged = { ...existing, ...context, updatedAt: new Date().toISOString() };
|
|
210
|
-
fs.writeFileSync(ctxPath, JSON.stringify(merged, null, 2));
|
|
211
|
-
return { content: [{ type: "text", text: `Context for @${agent} updated successfully.` }] };
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
return { content: [{ type: "text", text: "Failed to update agent context." }] };
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
};
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { getFrameworkDir } from "../utils.js";
|
|
4
|
-
import { SEND_AGENT_MESSAGE_ARGS_SCHEMA, READ_AGENT_MESSAGES_ARGS_SCHEMA, GET_AGENT_INBOX_STATS_ARGS_SCHEMA, UPDATE_AGENT_MESSAGE_STATUS_ARGS_SCHEMA } from "../schemas.js";
|
|
5
|
-
export const messageTools = [
|
|
6
|
-
{
|
|
7
|
-
name: "send_agent_message",
|
|
8
|
-
description: "Sends a message to another specialized agent following the Hermes Protocol.",
|
|
9
|
-
inputSchema: {
|
|
10
|
-
type: "object",
|
|
11
|
-
properties: {
|
|
12
|
-
from: { type: "string", description: "Sender agent name (e.g. @manager, @backend)" },
|
|
13
|
-
to: { type: "string", description: "Recipient agent name" },
|
|
14
|
-
message: { type: "string", description: "The message content" },
|
|
15
|
-
traceId: { type: "string", description: "The active Trace ID" },
|
|
16
|
-
category: { type: "string", enum: ["ACTION", "DELEGATION", "INFO", "ALERT"], default: "INFO" },
|
|
17
|
-
priority: { type: "string", enum: ["LOW", "MEDIUM", "HIGH", "URGENT"], default: "MEDIUM" },
|
|
18
|
-
},
|
|
19
|
-
required: ["from", "to", "message", "traceId"],
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
name: "read_agent_messages",
|
|
24
|
-
description: "Reads messages sent to the current agent. Filters by Trace ID if provided.",
|
|
25
|
-
inputSchema: {
|
|
26
|
-
type: "object",
|
|
27
|
-
properties: {
|
|
28
|
-
agent: { type: "string", description: "Current agent name reading their messages" },
|
|
29
|
-
traceId: { type: "string", description: "Optional Trace ID to filter messages" },
|
|
30
|
-
},
|
|
31
|
-
required: ["agent"],
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: "get_agent_inbox_stats",
|
|
36
|
-
description: "Returns statistics about the agent's inbox (total, unread, priority distribution).",
|
|
37
|
-
inputSchema: {
|
|
38
|
-
type: "object",
|
|
39
|
-
properties: {
|
|
40
|
-
agent: { type: "string", description: "Agent name" },
|
|
41
|
-
},
|
|
42
|
-
required: ["agent"],
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: "update_agent_message_status",
|
|
47
|
-
description: "Allows an agent to acknowledge, complete, or reject a Hermes message by Trace ID (advances the orchestration lifecycle).",
|
|
48
|
-
inputSchema: {
|
|
49
|
-
type: "object",
|
|
50
|
-
properties: {
|
|
51
|
-
agent: { type: "string", description: "The agent performing the status update" },
|
|
52
|
-
traceId: { type: "string", description: "Trace ID of the message to update" },
|
|
53
|
-
newStatus: { type: "string", enum: ["ACKNOWLEDGED", "COMPLETED", "REJECTED"], description: "New lifecycle status" },
|
|
54
|
-
},
|
|
55
|
-
required: ["agent", "traceId", "newStatus"],
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
];
|
|
59
|
-
function normalizeAgentName(agent) {
|
|
60
|
-
const normalized = agent.replace(/^@/, "").trim();
|
|
61
|
-
return /^[a-z0-9_-]+$/i.test(normalized) ? normalized : null;
|
|
62
|
-
}
|
|
63
|
-
export const messageHandlers = {
|
|
64
|
-
send_agent_message: async (args, projectRoot) => {
|
|
65
|
-
const parsed = SEND_AGENT_MESSAGE_ARGS_SCHEMA.safeParse(args ?? {});
|
|
66
|
-
if (!parsed.success)
|
|
67
|
-
return { content: [{ type: "text", text: "Invalid message arguments." }] };
|
|
68
|
-
try {
|
|
69
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
70
|
-
const recipient = normalizeAgentName(parsed.data.to);
|
|
71
|
-
if (!recipient)
|
|
72
|
-
return { content: [{ type: "text", text: "Invalid recipient agent name." }] };
|
|
73
|
-
const messagesDir = path.join(projectRoot, frameworkDir, "messages");
|
|
74
|
-
if (!fs.existsSync(messagesDir))
|
|
75
|
-
fs.mkdirSync(messagesDir, { recursive: true });
|
|
76
|
-
const messagePath = path.join(messagesDir, `${recipient}.json`);
|
|
77
|
-
const messages = (fs.existsSync(messagePath) ? JSON.parse(fs.readFileSync(messagePath, "utf-8")) : []);
|
|
78
|
-
const sender = normalizeAgentName(parsed.data.from) || "unknown";
|
|
79
|
-
messages.push({
|
|
80
|
-
timestamp: new Date().toISOString(),
|
|
81
|
-
from: sender,
|
|
82
|
-
to: recipient,
|
|
83
|
-
traceId: parsed.data.traceId,
|
|
84
|
-
category: parsed.data.category,
|
|
85
|
-
priority: parsed.data.priority,
|
|
86
|
-
content: parsed.data.message,
|
|
87
|
-
status: "PENDING"
|
|
88
|
-
});
|
|
89
|
-
fs.writeFileSync(messagePath, JSON.stringify(messages, null, 2));
|
|
90
|
-
return { content: [{ type: "text", text: `Hermes: Message sent to @${recipient} [${parsed.data.category} | ${parsed.data.priority}].` }] };
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
return { content: [{ type: "text", text: "Failed to send agent message." }] };
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
read_agent_messages: async (args, projectRoot) => {
|
|
97
|
-
const parsed = READ_AGENT_MESSAGES_ARGS_SCHEMA.safeParse(args ?? {});
|
|
98
|
-
if (!parsed.success)
|
|
99
|
-
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
100
|
-
try {
|
|
101
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
102
|
-
const agentName = normalizeAgentName(parsed.data.agent);
|
|
103
|
-
if (!agentName)
|
|
104
|
-
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
105
|
-
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
106
|
-
if (!fs.existsSync(messagePath))
|
|
107
|
-
return { content: [{ type: "text", text: "No messages found." }] };
|
|
108
|
-
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8"));
|
|
109
|
-
const pending = messages.filter((m) => (m.status === "PENDING" || m.status === "ACKNOWLEDGED") &&
|
|
110
|
-
(!parsed.data.traceId || m.traceId === parsed.data.traceId));
|
|
111
|
-
// Transition PENDING → READ (Do not regress ACKNOWLEDGED messages back to READ)
|
|
112
|
-
const updated = messages.map((m) => {
|
|
113
|
-
if (m.status === "PENDING" &&
|
|
114
|
-
(!parsed.data.traceId || m.traceId === parsed.data.traceId)) {
|
|
115
|
-
return { ...m, status: "READ" };
|
|
116
|
-
}
|
|
117
|
-
return m;
|
|
118
|
-
});
|
|
119
|
-
fs.writeFileSync(messagePath, JSON.stringify(updated, null, 2));
|
|
120
|
-
if (pending.length === 0)
|
|
121
|
-
return { content: [{ type: "text", text: "No new messages." }] };
|
|
122
|
-
const formatted = pending.map((m) => `- [${m.priority}] **${m.from}** (${m.category}): ${m.content} *(Trace: ${m.traceId})*`).join("\n");
|
|
123
|
-
return { content: [{ type: "text", text: `### HERMES INBOX: @${agentName}\n\n${formatted}` }] };
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return { content: [{ type: "text", text: "Failed to read agent messages." }] };
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
get_agent_inbox_stats: async (args, projectRoot) => {
|
|
130
|
-
const parsed = GET_AGENT_INBOX_STATS_ARGS_SCHEMA.safeParse(args ?? {});
|
|
131
|
-
if (!parsed.success)
|
|
132
|
-
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
133
|
-
try {
|
|
134
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
135
|
-
const agentName = normalizeAgentName(parsed.data.agent);
|
|
136
|
-
if (!agentName)
|
|
137
|
-
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
138
|
-
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
139
|
-
if (!fs.existsSync(messagePath))
|
|
140
|
-
return { content: [{ type: "text", text: "Inbox empty." }] };
|
|
141
|
-
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8"));
|
|
142
|
-
const pendingOrRead = messages.filter(m => m.status === "PENDING" || m.status === "READ");
|
|
143
|
-
const priorityDist = messages.reduce((acc, m) => {
|
|
144
|
-
acc[m.priority] = (acc[m.priority] || 0) + 1;
|
|
145
|
-
return acc;
|
|
146
|
-
}, {});
|
|
147
|
-
const statusDist = messages.reduce((acc, m) => {
|
|
148
|
-
acc[m.status] = (acc[m.status] || 0) + 1;
|
|
149
|
-
return acc;
|
|
150
|
-
}, {});
|
|
151
|
-
return {
|
|
152
|
-
content: [{
|
|
153
|
-
type: "text",
|
|
154
|
-
text: `### INBOX STATS: @${agentName}\n- Total: ${messages.length}\n- Pending/Read: ${pendingOrRead.length}\n- By Priority: ${JSON.stringify(priorityDist)}\n- By Status: ${JSON.stringify(statusDist)}`
|
|
155
|
-
}]
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
return { content: [{ type: "text", text: "Failed to read agent messages." }] };
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
update_agent_message_status: async (args, projectRoot) => {
|
|
163
|
-
const parsed = UPDATE_AGENT_MESSAGE_STATUS_ARGS_SCHEMA.safeParse(args ?? {});
|
|
164
|
-
if (!parsed.success)
|
|
165
|
-
return { content: [{ type: "text", text: "Invalid arguments for status update." }] };
|
|
166
|
-
try {
|
|
167
|
-
const frameworkDir = getFrameworkDir(projectRoot);
|
|
168
|
-
const agentName = normalizeAgentName(parsed.data.agent);
|
|
169
|
-
if (!agentName)
|
|
170
|
-
return { content: [{ type: "text", text: "Invalid agent name." }] };
|
|
171
|
-
const messagePath = path.join(projectRoot, frameworkDir, "messages", `${agentName}.json`);
|
|
172
|
-
if (!fs.existsSync(messagePath))
|
|
173
|
-
return { content: [{ type: "text", text: "No messages for this agent." }] };
|
|
174
|
-
const messages = JSON.parse(fs.readFileSync(messagePath, "utf-8"));
|
|
175
|
-
let updated = false;
|
|
176
|
-
const newMessages = messages.map((m) => {
|
|
177
|
-
if (m.traceId === parsed.data.traceId) {
|
|
178
|
-
updated = true;
|
|
179
|
-
return { ...m, status: parsed.data.newStatus };
|
|
180
|
-
}
|
|
181
|
-
return m;
|
|
182
|
-
});
|
|
183
|
-
if (!updated) {
|
|
184
|
-
return { content: [{ type: "text", text: `No message found with Trace ID ${parsed.data.traceId}.` }] };
|
|
185
|
-
}
|
|
186
|
-
fs.writeFileSync(messagePath, JSON.stringify(newMessages, null, 2));
|
|
187
|
-
return { content: [{ type: "text", text: `Hermes: Message ${parsed.data.traceId} marked as ${parsed.data.newStatus}.` }] };
|
|
188
|
-
}
|
|
189
|
-
catch {
|
|
190
|
-
return { content: [{ type: "text", text: "Failed to update message status." }] };
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
};
|