agent-enderun 1.1.10 → 1.10.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.
Files changed (254) hide show
  1. package/README.md +294 -19
  2. package/bin/cli.js +7 -0
  3. package/bin/enderun-dashboard.js +16 -857
  4. package/bin/validate-agent-army.js +12 -12
  5. package/dist/framework-mcp/src/index.js +14 -2
  6. package/dist/framework-mcp/src/index.js.map +1 -1
  7. package/dist/framework-mcp/src/tools/file_system/batch_surgical_edit.js +2 -1
  8. package/dist/framework-mcp/src/tools/file_system/batch_surgical_edit.js.map +1 -1
  9. package/dist/framework-mcp/src/tools/file_system/replace_text.js +2 -1
  10. package/dist/framework-mcp/src/tools/file_system/replace_text.js.map +1 -1
  11. package/dist/framework-mcp/src/tools/file_system/write_file.js +3 -3
  12. package/dist/framework-mcp/src/tools/file_system/write_file.js.map +1 -1
  13. package/dist/framework-mcp/src/utils/fs.d.ts +16 -0
  14. package/dist/framework-mcp/src/utils/fs.js +45 -0
  15. package/dist/framework-mcp/src/utils/fs.js.map +1 -0
  16. package/dist/framework-mcp/src/utils/metrics.js.map +1 -1
  17. package/dist/src/cli/adapters/core.js.map +1 -1
  18. package/dist/src/cli/adapters/scaffold.js +4 -3
  19. package/dist/src/cli/adapters/scaffold.js.map +1 -1
  20. package/dist/src/cli/commands/app.js +20 -5
  21. package/dist/src/cli/commands/app.js.map +1 -1
  22. package/dist/src/cli/commands/approve.d.ts +1 -1
  23. package/dist/src/cli/commands/approve.js +7 -7
  24. package/dist/src/cli/commands/approve.js.map +1 -1
  25. package/dist/src/cli/commands/check.d.ts +1 -1
  26. package/dist/src/cli/commands/check.js +35 -4
  27. package/dist/src/cli/commands/check.js.map +1 -1
  28. package/dist/src/cli/commands/compliance.d.ts +1 -1
  29. package/dist/src/cli/commands/compliance.js +1 -1
  30. package/dist/src/cli/commands/compliance.js.map +1 -1
  31. package/dist/src/cli/commands/contract.d.ts +2 -2
  32. package/dist/src/cli/commands/contract.js +8 -14
  33. package/dist/src/cli/commands/contract.js.map +1 -1
  34. package/dist/src/cli/commands/dashboard/data-service.d.ts +33 -0
  35. package/dist/src/cli/commands/dashboard/data-service.js +120 -0
  36. package/dist/src/cli/commands/dashboard/data-service.js.map +1 -0
  37. package/dist/src/cli/commands/dashboard/html-template.d.ts +4 -0
  38. package/dist/src/cli/commands/dashboard/html-template.js +697 -0
  39. package/dist/src/cli/commands/dashboard/html-template.js.map +1 -0
  40. package/dist/src/cli/commands/dashboard/server.d.ts +2 -0
  41. package/dist/src/cli/commands/dashboard/server.js +38 -0
  42. package/dist/src/cli/commands/dashboard/server.js.map +1 -0
  43. package/dist/src/cli/commands/dashboard.d.ts +4 -0
  44. package/dist/src/cli/commands/dashboard.js +10 -0
  45. package/dist/src/cli/commands/dashboard.js.map +1 -0
  46. package/dist/src/cli/commands/explorer.d.ts +2 -2
  47. package/dist/src/cli/commands/explorer.js +2 -2
  48. package/dist/src/cli/commands/explorer.js.map +1 -1
  49. package/dist/src/cli/commands/git.d.ts +2 -2
  50. package/dist/src/cli/commands/git.js +2 -2
  51. package/dist/src/cli/commands/git.js.map +1 -1
  52. package/dist/src/cli/commands/init/scaffold-core.d.ts +4 -0
  53. package/dist/src/cli/commands/init/scaffold-core.js +65 -0
  54. package/dist/src/cli/commands/init/scaffold-core.js.map +1 -0
  55. package/dist/src/cli/commands/init/scaffold-ops.d.ts +1 -0
  56. package/dist/src/cli/commands/init/scaffold-ops.js +83 -0
  57. package/dist/src/cli/commands/init/scaffold-ops.js.map +1 -0
  58. package/dist/src/cli/commands/init/scaffold-standards.d.ts +2 -0
  59. package/dist/src/cli/commands/init/scaffold-standards.js +58 -0
  60. package/dist/src/cli/commands/init/scaffold-standards.js.map +1 -0
  61. package/dist/src/cli/commands/init.js +14 -275
  62. package/dist/src/cli/commands/init.js.map +1 -1
  63. package/dist/src/cli/commands/knowledge.d.ts +2 -2
  64. package/dist/src/cli/commands/knowledge.js +4 -3
  65. package/dist/src/cli/commands/knowledge.js.map +1 -1
  66. package/dist/src/cli/commands/lint.d.ts +1 -1
  67. package/dist/src/cli/commands/lint.js +2 -3
  68. package/dist/src/cli/commands/lint.js.map +1 -1
  69. package/dist/src/cli/commands/log.d.ts +1 -1
  70. package/dist/src/cli/commands/log.js +3 -2
  71. package/dist/src/cli/commands/log.js.map +1 -1
  72. package/dist/src/cli/commands/memory.d.ts +1 -1
  73. package/dist/src/cli/commands/memory.js +1 -1
  74. package/dist/src/cli/commands/memory.js.map +1 -1
  75. package/dist/src/cli/commands/orchestrate.js +169 -140
  76. package/dist/src/cli/commands/orchestrate.js.map +1 -1
  77. package/dist/src/cli/commands/plan.d.ts +1 -1
  78. package/dist/src/cli/commands/plan.js +1 -1
  79. package/dist/src/cli/commands/plan.js.map +1 -1
  80. package/dist/src/cli/commands/script.d.ts +1 -1
  81. package/dist/src/cli/commands/script.js +1 -1
  82. package/dist/src/cli/commands/script.js.map +1 -1
  83. package/dist/src/cli/commands/security.d.ts +1 -1
  84. package/dist/src/cli/commands/security.js +1 -1
  85. package/dist/src/cli/commands/security.js.map +1 -1
  86. package/dist/src/cli/commands/status.d.ts +1 -1
  87. package/dist/src/cli/commands/status.js +1 -1
  88. package/dist/src/cli/commands/status.js.map +1 -1
  89. package/dist/src/cli/commands/trace.d.ts +1 -1
  90. package/dist/src/cli/commands/trace.js +1 -1
  91. package/dist/src/cli/commands/trace.js.map +1 -1
  92. package/dist/src/cli/index.js +36 -21
  93. package/dist/src/cli/index.js.map +1 -1
  94. package/dist/src/cli/shims.js +6 -6
  95. package/dist/src/cli/utils/app-backend.js +7 -0
  96. package/dist/src/cli/utils/app-backend.js.map +1 -1
  97. package/dist/src/cli/utils/app-docs.js +23 -5
  98. package/dist/src/cli/utils/app-docs.js.map +1 -1
  99. package/dist/src/cli/utils/app-frontend.js +5 -0
  100. package/dist/src/cli/utils/app-frontend.js.map +1 -1
  101. package/dist/src/cli/utils/app-inferrer.d.ts +1 -0
  102. package/dist/src/cli/utils/app-inferrer.js +12 -1
  103. package/dist/src/cli/utils/app-inferrer.js.map +1 -1
  104. package/dist/src/cli/utils/app-mobile.d.ts +2 -0
  105. package/dist/src/cli/utils/app-mobile.js +106 -0
  106. package/dist/src/cli/utils/app-mobile.js.map +1 -0
  107. package/dist/src/cli/utils/app-types.js +8 -4
  108. package/dist/src/cli/utils/app-types.js.map +1 -1
  109. package/dist/src/cli/utils/app.d.ts +1 -0
  110. package/dist/src/cli/utils/app.js +1 -0
  111. package/dist/src/cli/utils/app.js.map +1 -1
  112. package/dist/src/cli/utils/claude.js +2 -1
  113. package/dist/src/cli/utils/claude.js.map +1 -1
  114. package/dist/src/cli/utils/compliance.js +37 -6
  115. package/dist/src/cli/utils/compliance.js.map +1 -1
  116. package/dist/src/cli/utils/config-schema.d.ts +3 -3
  117. package/dist/src/cli/utils/config-schema.js +3 -3
  118. package/dist/src/cli/utils/config-schema.js.map +1 -1
  119. package/dist/src/cli/utils/fs.d.ts +2 -2
  120. package/dist/src/cli/utils/fs.js +9 -13
  121. package/dist/src/cli/utils/fs.js.map +1 -1
  122. package/dist/src/cli/utils/memory.d.ts +9 -2
  123. package/dist/src/cli/utils/memory.js +87 -16
  124. package/dist/src/cli/utils/memory.js.map +1 -1
  125. package/dist/src/cli/utils/pkg.js +14 -13
  126. package/dist/src/cli/utils/pkg.js.map +1 -1
  127. package/dist/src/cli/utils/string.js +1 -1
  128. package/dist/src/cli/utils/string.js.map +1 -1
  129. package/dist/src/cli/utils/ui.d.ts +1 -1
  130. package/dist/src/cli/utils/ui.js +1 -1
  131. package/dist/src/modules/adapters/shared.js +7 -7
  132. package/dist/src/modules/adapters/shared.js.map +1 -1
  133. package/dist/src/modules/agents/definitions.d.ts +3 -34
  134. package/dist/src/modules/agents/definitions.js +35 -511
  135. package/dist/src/modules/agents/definitions.js.map +1 -1
  136. package/dist/src/modules/agents/registry/analyst.d.ts +2 -0
  137. package/dist/src/modules/agents/registry/analyst.js +36 -0
  138. package/dist/src/modules/agents/registry/analyst.js.map +1 -0
  139. package/dist/src/modules/agents/registry/architect.d.ts +2 -0
  140. package/dist/src/modules/agents/registry/architect.js +42 -0
  141. package/dist/src/modules/agents/registry/architect.js.map +1 -0
  142. package/dist/src/modules/agents/registry/backend.d.ts +2 -0
  143. package/dist/src/modules/agents/registry/backend.js +43 -0
  144. package/dist/src/modules/agents/registry/backend.js.map +1 -0
  145. package/dist/src/modules/agents/registry/database.d.ts +2 -0
  146. package/dist/src/modules/agents/registry/database.js +38 -0
  147. package/dist/src/modules/agents/registry/database.js.map +1 -0
  148. package/dist/src/modules/agents/registry/devops.d.ts +2 -0
  149. package/dist/src/modules/agents/registry/devops.js +38 -0
  150. package/dist/src/modules/agents/registry/devops.js.map +1 -0
  151. package/dist/src/modules/agents/registry/explorer.d.ts +2 -0
  152. package/dist/src/modules/agents/registry/explorer.js +36 -0
  153. package/dist/src/modules/agents/registry/explorer.js.map +1 -0
  154. package/dist/src/modules/agents/registry/frontend.d.ts +2 -0
  155. package/dist/src/modules/agents/registry/frontend.js +44 -0
  156. package/dist/src/modules/agents/registry/frontend.js.map +1 -0
  157. package/dist/src/modules/agents/registry/git.d.ts +2 -0
  158. package/dist/src/modules/agents/registry/git.js +35 -0
  159. package/dist/src/modules/agents/registry/git.js.map +1 -0
  160. package/dist/src/modules/agents/registry/manager.d.ts +2 -0
  161. package/dist/src/modules/agents/registry/manager.js +49 -0
  162. package/dist/src/modules/agents/registry/manager.js.map +1 -0
  163. package/dist/src/modules/agents/registry/mobile.d.ts +2 -0
  164. package/dist/src/modules/agents/registry/mobile.js +40 -0
  165. package/dist/src/modules/agents/registry/mobile.js.map +1 -0
  166. package/dist/src/modules/agents/registry/native.d.ts +2 -0
  167. package/dist/src/modules/agents/registry/native.js +36 -0
  168. package/dist/src/modules/agents/registry/native.js.map +1 -0
  169. package/dist/src/modules/agents/registry/quality.d.ts +2 -0
  170. package/dist/src/modules/agents/registry/quality.js +41 -0
  171. package/dist/src/modules/agents/registry/quality.js.map +1 -0
  172. package/dist/src/modules/agents/registry/security.d.ts +2 -0
  173. package/dist/src/modules/agents/registry/security.js +37 -0
  174. package/dist/src/modules/agents/registry/security.js.map +1 -0
  175. package/dist/src/modules/agents/types.d.ts +31 -0
  176. package/dist/src/modules/agents/types.js +2 -0
  177. package/dist/src/modules/agents/types.js.map +1 -0
  178. package/dist/src/modules/skills/definitions.d.ts +1 -1
  179. package/dist/src/modules/skills/definitions.js +1 -1
  180. package/dist/src/shared/errors.d.ts +8 -6
  181. package/dist/src/shared/errors.js +13 -10
  182. package/dist/src/shared/errors.js.map +1 -1
  183. package/dist/src/shared/fs.d.ts +4 -0
  184. package/dist/src/shared/fs.js +17 -2
  185. package/dist/src/shared/fs.js.map +1 -1
  186. package/dist/src/shared/types.d.ts +19 -0
  187. package/dist/src/shared/types.js +13 -0
  188. package/dist/src/shared/types.js.map +1 -0
  189. package/package.json +3 -5
  190. package/src/cli/adapters/core.ts +0 -1
  191. package/src/cli/adapters/scaffold.ts +4 -3
  192. package/src/cli/commands/app.ts +23 -5
  193. package/src/cli/commands/approve.ts +15 -7
  194. package/src/cli/commands/check.ts +38 -5
  195. package/src/cli/commands/compliance.ts +1 -1
  196. package/src/cli/commands/contract.ts +20 -14
  197. package/src/cli/commands/dashboard/data-service.ts +119 -0
  198. package/src/cli/commands/dashboard/html-template.ts +696 -0
  199. package/src/cli/commands/dashboard/server.ts +43 -0
  200. package/src/cli/commands/dashboard.ts +11 -0
  201. package/src/cli/commands/explorer.ts +2 -2
  202. package/src/cli/commands/git.ts +2 -2
  203. package/src/cli/commands/init/scaffold-core.ts +71 -0
  204. package/src/cli/commands/init/scaffold-ops.ts +86 -0
  205. package/src/cli/commands/init/scaffold-standards.ts +59 -0
  206. package/src/cli/commands/init.ts +21 -292
  207. package/src/cli/commands/knowledge.ts +5 -3
  208. package/src/cli/commands/lint.ts +2 -4
  209. package/src/cli/commands/log.ts +4 -2
  210. package/src/cli/commands/memory.ts +1 -1
  211. package/src/cli/commands/orchestrate.ts +177 -142
  212. package/src/cli/commands/plan.ts +1 -1
  213. package/src/cli/commands/script.ts +1 -1
  214. package/src/cli/commands/security.ts +1 -1
  215. package/src/cli/commands/status.ts +1 -1
  216. package/src/cli/commands/trace.ts +1 -1
  217. package/src/cli/index.ts +37 -21
  218. package/src/cli/shims.ts +6 -6
  219. package/src/cli/utils/app-backend.ts +8 -0
  220. package/src/cli/utils/app-docs.ts +23 -5
  221. package/src/cli/utils/app-frontend.ts +6 -0
  222. package/src/cli/utils/app-inferrer.ts +11 -1
  223. package/src/cli/utils/app-mobile.ts +113 -0
  224. package/src/cli/utils/app-types.ts +9 -4
  225. package/src/cli/utils/app.ts +1 -0
  226. package/src/cli/utils/claude.ts +2 -1
  227. package/src/cli/utils/compliance.ts +38 -7
  228. package/src/cli/utils/config-schema.ts +4 -4
  229. package/src/cli/utils/fs.ts +9 -14
  230. package/src/cli/utils/memory.ts +103 -20
  231. package/src/cli/utils/pkg.ts +14 -13
  232. package/src/cli/utils/string.ts +1 -1
  233. package/src/cli/utils/ui.ts +1 -1
  234. package/src/modules/adapters/shared.ts +7 -7
  235. package/src/modules/agents/definitions.ts +39 -598
  236. package/src/modules/agents/registry/analyst.ts +40 -0
  237. package/src/modules/agents/registry/architect.ts +46 -0
  238. package/src/modules/agents/registry/backend.ts +47 -0
  239. package/src/modules/agents/registry/database.ts +42 -0
  240. package/src/modules/agents/registry/devops.ts +42 -0
  241. package/src/modules/agents/registry/explorer.ts +40 -0
  242. package/src/modules/agents/registry/frontend.ts +48 -0
  243. package/src/modules/agents/registry/git.ts +39 -0
  244. package/src/modules/agents/registry/manager.ts +53 -0
  245. package/src/modules/agents/registry/mobile.ts +44 -0
  246. package/src/modules/agents/registry/native.ts +40 -0
  247. package/src/modules/agents/registry/quality.ts +45 -0
  248. package/src/modules/agents/registry/security.ts +41 -0
  249. package/src/modules/agents/types.ts +37 -0
  250. package/src/modules/skills/definitions.ts +1 -1
  251. package/src/shared/errors.ts +13 -10
  252. package/src/shared/fs.ts +17 -2
  253. package/src/shared/types.ts +20 -0
  254. package/bin/utils/docgen.ts +0 -24
@@ -3,9 +3,11 @@ import path from "path";
3
3
  import chalk from "chalk";
4
4
  import { getFrameworkDir, updateDocumentStore, readStatus } from "../utils/memory.js";
5
5
  import { UI } from "../utils/ui.js";
6
+ import { writeTextFile, appendFile } from "../utils/fs.js";
6
7
  import { z } from "zod";
7
8
  import { logger } from "../../shared/logger.js";
8
9
  import { TaskRequestSchema } from "../../contracts/tasks.js";
10
+ import { sleep } from "../utils/time.js";
9
11
 
10
12
  export const HermesMessageSchema = z.object({
11
13
  timestamp: z.string(),
@@ -22,174 +24,191 @@ export const HermesMessageSchema = z.object({
22
24
 
23
25
  export type HermesMessage = z.infer<typeof HermesMessageSchema>;
24
26
 
27
+ // Global status cache for in-memory optimization
28
+ let agentStatusCache: Record<string, { state: string; task: string; lastUpdated?: string }> = {};
29
+ let isLooping = false;
30
+
25
31
  export async function orchestrateCommand() {
32
+ UI.intent("Hermes Message Broker", "Starting the live agent orchestration loop...");
33
+
26
34
  const frameworkDir = getFrameworkDir();
27
35
  const messagesDir = path.join(frameworkDir, "messages");
28
36
 
29
- // Check for agent timeouts (e.g. 30 minutes in EXECUTING state)
30
- const agentStatuses = readStatus();
31
- let updatedStatus = false;
32
- for (const [agentName, info] of Object.entries(agentStatuses)) {
33
- const { state, lastUpdated } = info as { state: string; task: string; lastUpdated?: string };
34
- if (state === "EXECUTING" && lastUpdated) {
35
- const lastUpdatedTime = Date.parse(lastUpdated);
36
- if (!isNaN(lastUpdatedTime) && Date.now() - lastUpdatedTime > 30 * 60 * 1000) {
37
- UI.error(`āš ļø Agent ${agentName} has TIMED OUT (in EXECUTING state for >30m). Transitioning to BLOCKED.`);
38
- agentStatuses[agentName] = { state: "BLOCKED", task: "Timeout occurred", lastUpdated: new Date().toISOString() };
39
- updatedStatus = true;
40
- }
37
+ // Graceful Shutdown Handling
38
+ const shutdown = async () => {
39
+ if (!isLooping) return;
40
+ isLooping = false;
41
+ UI.warning("\nšŸ›‘ Shutdown signal received. Cleaning up Hermes loop...");
42
+ // Additional cleanup like releasing global locks could go here
43
+ process.exit(0);
44
+ };
45
+
46
+ process.on("SIGINT", shutdown);
47
+ process.on("SIGTERM", shutdown);
48
+
49
+ isLooping = true;
50
+
51
+ while (isLooping) {
52
+ // Initial load from disk to sync the cache
53
+ if (Object.keys(agentStatusCache).length === 0) {
54
+ const diskStatus = readStatus();
55
+ agentStatusCache = diskStatus || {};
41
56
  }
42
- }
43
- if (updatedStatus) {
44
- updateDocumentStore("status", agentStatuses);
45
- }
46
57
 
47
- if (!fs.existsSync(messagesDir)) {
48
- UI.success("No messages to process yet.");
49
- return;
50
- }
58
+ // Check for agent timeouts (e.g. 30 minutes in EXECUTING state)
59
+ const agentStatuses = readStatus();
60
+ let updatedStatus = false;
61
+ for (const [agentName, info] of Object.entries(agentStatuses)) {
62
+ const { state, lastUpdated } = info as { state: string; task: string; lastUpdated?: string };
63
+ if (state === "EXECUTING" && lastUpdated) {
64
+ const lastUpdatedTime = Date.parse(lastUpdated);
65
+ if (!isNaN(lastUpdatedTime) && Date.now() - lastUpdatedTime > 30 * 60 * 1000) {
66
+ UI.error(`āš ļø Agent ${agentName} has TIMED OUT (in EXECUTING state for >30m). Transitioning to BLOCKED.`);
67
+ agentStatuses[agentName] = { state: "BLOCKED", task: "Timeout occurred", lastUpdated: new Date().toISOString() };
68
+ updatedStatus = true;
69
+ }
70
+ }
71
+ }
72
+ if (updatedStatus) {
73
+ updateDocumentStore("status", agentStatuses);
74
+ agentStatusCache = { ...agentStatuses }; // Sync cache
75
+ }
51
76
 
52
- const messageFiles = fs.readdirSync(messagesDir).filter((f) => f.endsWith(".json"));
53
- const pendingMessages: HermesMessage[] = [];
54
- const fileMessagesMap: Record<string, HermesMessage[]> = {};
77
+ if (fs.existsSync(messagesDir)) {
78
+ const messageFiles = fs.readdirSync(messagesDir).filter((f) => f.endsWith(".json"));
79
+ const pendingMessages: HermesMessage[] = [];
80
+ const fileMessagesMap: Record<string, HermesMessage[]> = {};
55
81
 
56
- for (const file of messageFiles) {
57
- const filePath = path.join(messagesDir, file);
58
- try {
59
- const content = fs.readFileSync(filePath, "utf8").trim();
60
- if (!content) continue;
61
- const lines = content.split("\n");
62
- const allMsgs: HermesMessage[] = [];
63
- lines.forEach((line) => {
64
- if (!line.trim()) return;
82
+ for (const file of messageFiles) {
83
+ const filePath = path.join(messagesDir, file);
65
84
  try {
66
- const parsed = JSON.parse(line);
67
- const msg = HermesMessageSchema.parse(parsed);
68
-
69
- // Contract Validation
70
- if (msg.category === "ACTION" || msg.category === "DELEGATION") {
85
+ const content = fs.readFileSync(filePath, "utf8").trim();
86
+ if (!content) continue;
87
+ const lines = content.split("\n");
88
+ const allMsgs: HermesMessage[] = [];
89
+ lines.forEach((line) => {
90
+ if (!line.trim()) return;
71
91
  try {
72
- const payload = JSON.parse(msg.content);
73
- TaskRequestSchema.parse(payload);
92
+ const parsed = JSON.parse(line);
93
+ const msg = HermesMessageSchema.parse(parsed);
94
+
95
+ // Contract Validation
96
+ if (msg.category === "ACTION" || msg.category === "DELEGATION") {
97
+ try {
98
+ const payload = JSON.parse(msg.content);
99
+ TaskRequestSchema.parse(payload);
100
+ } catch (e) {
101
+ UI.error(`Invalid task contract payload: ${(e as Error).message}`);
102
+ return; // Skip invalid message
103
+ }
104
+ }
105
+
106
+ if (msg.status === "PENDING") {
107
+ pendingMessages.push(msg);
108
+ }
109
+ allMsgs.push(msg);
74
110
  } catch (e) {
75
- UI.error(`Invalid task contract payload: ${(e as Error).message}`);
76
- return; // Skip invalid message
111
+ UI.error(`Skipping invalid Hermes message format: ${(e as Error).message}`);
77
112
  }
78
- }
79
-
80
- if (msg.status === "PENDING") {
81
- pendingMessages.push(msg);
82
- }
83
- allMsgs.push(msg);
84
- } catch (e) {
85
- UI.error(`Skipping invalid Hermes message format: ${(e as Error).message}`);
113
+ });
114
+ fileMessagesMap[file] = allMsgs;
115
+ } catch (err) {
116
+ UI.error(`Error occurred while reading ${file}.`);
117
+ logger.debug(`Failed to read message file ${file}`, err);
86
118
  }
87
- });
88
- fileMessagesMap[file] = allMsgs;
89
- } catch (err) {
90
- UI.error(`Error occurred while reading ${file}.`);
91
- logger.debug(`Failed to read message file ${file}`, err);
92
- }
93
- }
94
-
95
- if (pendingMessages.length === 0) {
96
- UI.success("All messages processed, no pending tasks.");
97
- return;
98
- }
119
+ }
99
120
 
100
- // Sort by priority: HIGH -> NORMAL -> LOW
101
- const priorityOrder = { HIGH: 1, NORMAL: 2, LOW: 3 };
102
- pendingMessages.sort((a, b) => {
103
- const pA = priorityOrder[a.priority || "NORMAL"] || 2;
104
- const pB = priorityOrder[b.priority || "NORMAL"] || 2;
105
- if (pA !== pB) return pA - pB;
106
- return Date.parse(a.timestamp) - Date.parse(b.timestamp);
107
- });
121
+ if (pendingMessages.length > 0) {
122
+ // Sort by priority: HIGH -> NORMAL -> LOW
123
+ const priorityOrder = { HIGH: 1, NORMAL: 2, LOW: 3 };
124
+ pendingMessages.sort((a, b) => {
125
+ const pA = priorityOrder[a.priority || "NORMAL"] || 2;
126
+ const pB = priorityOrder[b.priority || "NORMAL"] || 2;
127
+ if (pA !== pB) return pA - pB;
128
+ return Date.parse(a.timestamp) - Date.parse(b.timestamp);
129
+ });
108
130
 
109
- UI.intent("Multi-Agent Orchestration", `${pendingMessages.length} pending tasks and collaboration messages found.`);
131
+ UI.intent("Multi-Agent Orchestration", `${pendingMessages.length} pending tasks and collaboration messages found.`);
110
132
 
111
- const statusChanges: Record<string, { state: string; task: string }> = {};
133
+ const statusChanges: Record<string, { state: string; task: string }> = {};
112
134
 
113
- for (const msg of pendingMessages) {
114
- const { from, to, content, traceId, category, parentId } = msg;
135
+ for (const msg of pendingMessages) {
136
+ const { from, to, content, traceId, category, parentId } = msg;
115
137
 
116
- // Visual distinction for Sub-tasks
117
- const isSubtask = category === "SUBTASK" || !!parentId;
118
- const agentLabel = isSubtask ? `${to} (Sub-Agent)` : to;
138
+ const isSubtask = category === "SUBTASK" || !!parentId;
139
+ const agentLabel = isSubtask ? `${to} (Sub-Agent)` : to;
119
140
 
120
- UI.agentBox(agentLabel, content, `Trace: ${traceId} | From: ${from} | Category: ${category}`);
141
+ UI.agentBox(agentLabel, content, `Trace: ${traceId} | From: ${from} | Category: ${category}`);
121
142
 
122
- let targetState = "EXECUTING";
123
- if (category === "DELEGATION" || category === "SUBTASK") targetState = "BRIEFED";
143
+ let targetState = "EXECUTING";
144
+ if (category === "DELEGATION" || category === "SUBTASK") targetState = "BRIEFED";
124
145
 
125
- let taskDescription = content;
126
- if (category === "DELEGATION" || category === "ACTION") {
127
- try {
128
- const payload = JSON.parse(content);
129
- taskDescription = payload.task || content;
130
- } catch {
131
- // Ignore
132
- }
133
- }
134
-
135
- statusChanges[to] = { state: targetState, task: taskDescription };
146
+ let taskDescription = content;
147
+ if (category === "DELEGATION" || category === "ACTION") {
148
+ try {
149
+ const payload = JSON.parse(content);
150
+ taskDescription = payload.task || content;
151
+ } catch {
152
+ // Ignore
153
+ }
154
+ }
136
155
 
137
- if (targetState === "BRIEFED") {
138
- UI.success(`${to} informed and took over the task.`);
139
- statusChanges[to] = { state: "EXECUTING", task: taskDescription };
140
- }
156
+ statusChanges[to] = { state: targetState, task: taskDescription };
141
157
 
142
- if (isSubtask) {
143
- UI.success(`Sub-task connection established (Parent: ${parentId || "Unknown"}).`);
144
- }
145
-
146
- UI.success(`Collaboration initiated: ${from} -> ${to}`);
147
- console.warn(chalk.gray(` šŸ‘‰ Instructions: ${frameworkDir}/agents/${to.replace("@", "")}.md`));
158
+ if (targetState === "BRIEFED") {
159
+ UI.success(`${to} informed and took over the task.`);
160
+ statusChanges[to] = { state: "EXECUTING", task: taskDescription };
161
+ }
148
162
 
149
- // Mark message as processed
150
- msg.status = "PROCESSED";
151
- }
163
+ if (isSubtask) {
164
+ UI.success(`Sub-task connection established (Parent: ${parentId || "Unknown"}).`);
165
+ }
152
166
 
153
- // Apply batch status updates
154
- applyBatchStatusUpdates(statusChanges);
167
+ UI.success(`Collaboration initiated: ${from} -> ${to}`);
168
+ console.warn(chalk.gray(` šŸ‘‰ Instructions: ${frameworkDir}/agents/${to.replace("@", "")}.md`));
155
169
 
156
- // Write back updated message statuses to files and archive processed messages
157
- const archiveDir = path.join(messagesDir, "archive");
158
-
159
- for (const file of Object.keys(fileMessagesMap)) {
160
- const filePath = path.join(messagesDir, file);
161
- const msgs = fileMessagesMap[file];
162
-
163
- const activeMsgs = msgs.filter(m => m.status !== "PROCESSED");
164
- const processedMsgs = msgs.filter(m => m.status === "PROCESSED");
165
-
166
- try {
167
- // Write remaining active messages
168
- if (activeMsgs.length > 0) {
169
- const updatedContent = activeMsgs.map(m => JSON.stringify(m)).join("\n") + "\n";
170
- fs.writeFileSync(filePath, updatedContent, "utf8");
171
- } else {
172
- // Empty the file if no active messages
173
- if (fs.existsSync(filePath)) {
174
- fs.writeFileSync(filePath, "", "utf8");
170
+ msg.status = "PROCESSED";
175
171
  }
176
- }
177
172
 
178
- // Archive processed messages
179
- if (processedMsgs.length > 0) {
180
- if (!fs.existsSync(archiveDir)) {
181
- fs.mkdirSync(archiveDir, { recursive: true });
173
+ applyBatchStatusUpdates(statusChanges);
174
+
175
+ const archiveDir = path.join(messagesDir, "archive");
176
+
177
+ for (const file of Object.keys(fileMessagesMap)) {
178
+ const filePath = path.join(messagesDir, file);
179
+ const msgs = fileMessagesMap[file];
180
+
181
+ const activeMsgs = msgs.filter(m => m.status !== "PROCESSED");
182
+ const processedMsgs = msgs.filter(m => m.status === "PROCESSED");
183
+
184
+ try {
185
+ if (activeMsgs.length > 0) {
186
+ const updatedContent = activeMsgs.map(m => JSON.stringify(m)).join("\n") + "\n";
187
+ writeTextFile(filePath, updatedContent);
188
+ } else if (fs.existsSync(filePath)) {
189
+ writeTextFile(filePath, "");
190
+ }
191
+
192
+ if (processedMsgs.length > 0) {
193
+ if (!fs.existsSync(archiveDir)) {
194
+ fs.mkdirSync(archiveDir, { recursive: true });
195
+ }
196
+ const agentName = file.replace(".json", "");
197
+ const archivePath = path.join(archiveDir, `${agentName}_archive.json`);
198
+ const archiveContent = processedMsgs.map(m => JSON.stringify(m)).join("\n") + "\n";
199
+ appendFile(archivePath, archiveContent);
200
+ UI.success(`Archived ${processedMsgs.length} messages for @${agentName}`);
201
+ }
202
+ } catch (err) {
203
+ UI.error(`Error occurred while updating and archiving the message file ${file}.`);
204
+ logger.debug(`Failed to update and archive message file ${file}`, err);
205
+ }
182
206
  }
183
- const agentName = file.replace(".json", "");
184
- const archivePath = path.join(archiveDir, `${agentName}_archive.json`);
185
- const archiveContent = processedMsgs.map(m => JSON.stringify(m)).join("\n") + "\n";
186
- fs.appendFileSync(archivePath, archiveContent, "utf8");
187
- UI.success(`Archived ${processedMsgs.length} messages for @${agentName}`);
188
207
  }
189
- } catch (err) {
190
- UI.error(`Error occurred while updating and archiving the message file ${file}.`);
191
- logger.debug(`Failed to update and archive message file ${file}`, err);
192
208
  }
209
+
210
+ // Prevent 100% CPU and provide breathing room for I/O
211
+ await sleep(2000);
193
212
  }
194
213
  }
195
214
 
@@ -211,14 +230,15 @@ export async function sendMessage(args: {
211
230
  const messagePath = path.join(messagesDir, `${agentName}.json`);
212
231
  const lockPath = path.join(messagesDir, `${agentName}.lock`);
213
232
 
214
- let retries = 3;
233
+ let retries = 20; // Increased retries from 3 to 20
215
234
  let acquired = false;
216
235
  while (retries > 0) {
217
236
  try {
218
237
  if (fs.existsSync(lockPath)) {
219
238
  try {
220
239
  const stats = fs.statSync(lockPath);
221
- if (Date.now() - stats.mtimeMs > 5000) {
240
+ // Increased stale lock timeout from 5s to 10s for heavy ops
241
+ if (Date.now() - stats.mtimeMs > 10000) {
222
242
  fs.unlinkSync(lockPath);
223
243
  }
224
244
  } catch (err) {
@@ -236,6 +256,8 @@ export async function sendMessage(args: {
236
256
  if (error.code === "EEXIST") {
237
257
  retries--;
238
258
  if (retries > 0) {
259
+ // Exponential backoff or jitter could be added here,
260
+ // but fixed 500ms is usually fine for local file locks.
239
261
  await new Promise((resolve) => setTimeout(resolve, 500));
240
262
  }
241
263
  } else {
@@ -259,7 +281,7 @@ export async function sendMessage(args: {
259
281
  status: "PENDING"
260
282
  };
261
283
 
262
- fs.appendFileSync(messagePath, JSON.stringify(message) + "\n");
284
+ appendFile(messagePath, JSON.stringify(message) + "\n");
263
285
 
264
286
  return message;
265
287
  } catch (err) {
@@ -278,6 +300,19 @@ export async function sendMessage(args: {
278
300
 
279
301
 
280
302
  function applyBatchStatusUpdates(changes: Record<string, { state: string; task: string }>) {
281
- // Write to Document Store
282
- updateDocumentStore("status", changes);
303
+ let hasChanges = false;
304
+ const now = new Date().toISOString();
305
+
306
+ for (const [agent, data] of Object.entries(changes)) {
307
+ const current = agentStatusCache[agent];
308
+ if (!current || current.state !== data.state || current.task !== data.task) {
309
+ agentStatusCache[agent] = { ...data, lastUpdated: now };
310
+ hasChanges = true;
311
+ }
312
+ }
313
+
314
+ if (hasChanges) {
315
+ updateDocumentStore("status", agentStatusCache);
316
+ logger.debug("In-memory status cache synced to disk.");
317
+ }
283
318
  }
@@ -7,7 +7,7 @@ import { UI } from "../utils/ui.js";
7
7
  /**
8
8
  * Parses docs/user/roadmap.md and injects tasks into the Document Store.
9
9
  */
10
- export function planCommand() {
10
+ export async function planCommand() {
11
11
  const projectRoot = process.cwd();
12
12
  const roadmapPath = path.join(projectRoot, "docs/user/roadmap.md");
13
13
 
@@ -4,7 +4,7 @@ import cp from "child_process";
4
4
 
5
5
  const targetDir = process.cwd(); // Assuming targetDir is process.cwd() in the CLI context
6
6
 
7
- export function runScriptCommand(script: string, projectPath: string) {
7
+ export async function runScriptCommand(script: string, projectPath: string) {
8
8
  const fullPath = path.join(targetDir, projectPath);
9
9
  if (!fs.existsSync(fullPath)) {
10
10
  console.warn(`āŒ Project path not found: ${projectPath}`);
@@ -5,7 +5,7 @@ import { collectFiles } from "../utils/fs.js";
5
5
 
6
6
  const targetDir = process.cwd();
7
7
 
8
- export function securityAuditCommand(targetPath: string) {
8
+ export async function securityAuditCommand(targetPath: string) {
9
9
  console.warn(`šŸ” Running Advanced Security Audit on: ${targetPath}...`);
10
10
  const scanRules = [
11
11
  { pattern: /sql`/, message: "Potential Raw SQL usage detected", severity: "HIGH" },
@@ -5,7 +5,7 @@ import { listTasks, readState, readStatus } from "../utils/memory.js";
5
5
  /**
6
6
  * Print the current framework status.
7
7
  */
8
- export function statusCommand() {
8
+ export async function statusCommand() {
9
9
  const state = readState();
10
10
  const tasks = listTasks();
11
11
  const agentStatuses = readStatus();
@@ -4,7 +4,7 @@ import { sanitizeInput, normalizeAgentName, normalizePriority } from "../utils/s
4
4
  /**
5
5
  * Generate a new Trace ID and add it to project memory.
6
6
  */
7
- export function traceNewCommand(description: string, agent = "manager", priority = "P2"): string | void {
7
+ export async function traceNewCommand(description: string, agent = "manager", priority = "P2"): Promise<string | void> {
8
8
  const traceId = generateULID();
9
9
  const safeDescription = sanitizeInput(description);
10
10
  const safeAgent = normalizeAgentName(agent);
package/src/cli/index.ts CHANGED
@@ -71,21 +71,21 @@ async function main() {
71
71
  }
72
72
 
73
73
  case "check":
74
- checkCommand();
74
+ await checkCommand();
75
75
  break;
76
76
 
77
77
  case "plan":
78
- planCommand();
78
+ await planCommand();
79
79
  break;
80
80
  case "status":
81
- statusCommand();
81
+ await statusCommand();
82
82
  break;
83
83
 
84
84
  case "trace:new": {
85
85
  const description = args[1] || "Default task";
86
86
  const agent = args[2] || "manager";
87
87
  const priority = args[3] || "P1";
88
- traceNewCommand(description, agent, priority);
88
+ await traceNewCommand(description, agent, priority);
89
89
  break;
90
90
  }
91
91
 
@@ -96,7 +96,7 @@ async function main() {
96
96
  console.error("āŒ Error: section and content are required.");
97
97
  process.exit(64);
98
98
  }
99
- updateProjectMemoryCommand(section, content);
99
+ await updateProjectMemoryCommand(section, content);
100
100
  break;
101
101
  }
102
102
 
@@ -104,9 +104,15 @@ async function main() {
104
104
  await createAppCommand(args.slice(1));
105
105
  break;
106
106
 
107
+ case "dashboard": {
108
+ const { dashboardCommand } = await import("./commands/dashboard.js");
109
+ await dashboardCommand(args.slice(1));
110
+ break;
111
+ }
112
+
107
113
  case "orchestrate":
108
114
  case "loop":
109
- orchestrateCommand();
115
+ await orchestrateCommand();
110
116
  break;
111
117
 
112
118
  case "approve": {
@@ -115,16 +121,16 @@ async function main() {
115
121
  console.error("āŒ Error: traceId is required.");
116
122
  process.exit(64);
117
123
  }
118
- approveCommand(traceId);
124
+ await approveCommand(traceId);
119
125
  break;
120
126
  }
121
127
 
122
128
  case "verify-contract":
123
- verifyApiContractCommand();
129
+ await verifyApiContractCommand();
124
130
  break;
125
131
 
126
132
  case "update-contract":
127
- updateApiContractCommand();
133
+ await updateApiContractCommand();
128
134
  break;
129
135
 
130
136
  case "validate":
@@ -144,34 +150,34 @@ async function main() {
144
150
  const memoryContent = fs.existsSync(getMemoryPath()) ? fs.readFileSync(getMemoryPath(), "utf8") : "";
145
151
  const traceId = readActiveTraceId(memoryContent) || "T-000";
146
152
  const { gitCommitCommand } = await import("./commands/git.js");
147
- gitCommitCommand(traceId);
153
+ await gitCommitCommand(traceId);
148
154
  break;
149
155
  }
150
156
 
151
157
  case "git:sync": {
152
158
  const { gitSyncCommand } = await import("./commands/git.js");
153
- gitSyncCommand();
159
+ await gitSyncCommand();
154
160
  break;
155
161
  }
156
162
 
157
163
  case "check:compliance": {
158
164
  const targetPath = args[1] || "src";
159
165
  const { complianceCheckCommand } = await import("./commands/compliance.js");
160
- complianceCheckCommand(targetPath);
166
+ await complianceCheckCommand(targetPath);
161
167
  break;
162
168
  }
163
169
 
164
170
  case "explorer:graph": {
165
171
  const targetPath = args[1] || "src";
166
172
  const { explorerGraphCommand } = await import("./commands/explorer.js");
167
- explorerGraphCommand(targetPath);
173
+ await explorerGraphCommand(targetPath);
168
174
  break;
169
175
  }
170
176
 
171
177
  case "explorer:audit": {
172
178
  const targetPath = args[1] || "src";
173
179
  const { explorerAuditCommand } = await import("./commands/explorer.js");
174
- explorerAuditCommand(targetPath);
180
+ await explorerAuditCommand(targetPath);
175
181
  break;
176
182
  }
177
183
 
@@ -179,14 +185,14 @@ async function main() {
179
185
  const topic = args[1];
180
186
  const content = args[2];
181
187
  const { updateKnowledgeBaseCommand } = await import("./commands/knowledge.js");
182
- updateKnowledgeBaseCommand(topic, content);
188
+ await updateKnowledgeBaseCommand(topic, content);
183
189
  break;
184
190
  }
185
191
 
186
192
  case "knowledge:search": {
187
193
  const query = args[1];
188
194
  const { searchKnowledgeBaseCommand } = await import("./commands/knowledge.js");
189
- searchKnowledgeBaseCommand(query);
195
+ await searchKnowledgeBaseCommand(query);
190
196
  break;
191
197
  }
192
198
 
@@ -198,7 +204,7 @@ async function main() {
198
204
  const memoryContent = fs.existsSync(getMemoryPath()) ? fs.readFileSync(getMemoryPath(), "utf8") : "";
199
205
  const traceId = readActiveTraceId(memoryContent) || "T-000";
200
206
  const { logAgentActionCommand } = await import("./commands/log.js");
201
- logAgentActionCommand({ agent, action, status, summary, traceId });
207
+ await logAgentActionCommand({ agent, action, status, summary, traceId });
202
208
  break;
203
209
  }
204
210
 
@@ -206,20 +212,20 @@ async function main() {
206
212
  const script = args[1];
207
213
  const projectPath = args[2] || ".";
208
214
  const { runScriptCommand } = await import("./commands/script.js");
209
- runScriptCommand(script, projectPath);
215
+ await runScriptCommand(script, projectPath);
210
216
  break;
211
217
  }
212
218
 
213
219
  case "security:audit": {
214
220
  const targetPath = args[1] || "src";
215
221
  const { securityAuditCommand } = await import("./commands/security.js");
216
- securityAuditCommand(targetPath);
222
+ await securityAuditCommand(targetPath);
217
223
  break;
218
224
  }
219
225
 
220
226
  case "check:lint": {
221
227
  const { lintCommand } = await import("./commands/lint.js");
222
- lintCommand();
228
+ await lintCommand();
223
229
  break;
224
230
  }
225
231
 
@@ -267,7 +273,7 @@ async function main() {
267
273
 
268
274
  function showHelp() {
269
275
  console.warn(`
270
- šŸŽ–ļø Enderun Core CLI (v${getPackageVersion()}) — The Supreme AI Orchestrator
276
+ šŸŽ–ļø Agent Enderun CLI (v${getPackageVersion()}) — The Supreme AI Orchestrator
271
277
 
272
278
  Usage:
273
279
  agent-enderun <command> [options]
@@ -309,8 +315,18 @@ Example:
309
315
  }
310
316
 
311
317
  import { logger } from "../shared/logger.js";
318
+ import { EnderunBaseError } from "../shared/errors.js";
312
319
 
313
320
  main().catch((err) => {
321
+ if (err instanceof EnderunBaseError) {
322
+ console.error(`\nāŒ [${err.code}] Error: ${err.message}`);
323
+ if (err.solution) {
324
+ console.warn(`šŸ’” Solution Tip: ${err.solution}\n`);
325
+ }
326
+ } else {
327
+ console.error(`\nāŒ Fatal Error: ${err.message || String(err)}`);
328
+ }
329
+
314
330
  logger.fatal("Fatal exception during CLI execution", err);
315
331
  process.exit(1);
316
332
  });