closed-loop-cli 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.

Potentially problematic release.


This version of closed-loop-cli might be problematic. Click here for more details.

Files changed (86) hide show
  1. package/dist/dashboard/server.js +237 -0
  2. package/dist/index.js +272 -0
  3. package/dist/orchestrator/agent-prompts.js +42 -0
  4. package/dist/orchestrator/autogenesis.js +973 -0
  5. package/dist/orchestrator/dgm-archive.js +223 -0
  6. package/dist/orchestrator/event-stream.js +103 -0
  7. package/dist/orchestrator/fitness-evaluator.js +99 -0
  8. package/dist/orchestrator/meta-agent.js +421 -0
  9. package/dist/orchestrator/microagent-registry.js +134 -0
  10. package/dist/orchestrator/mutation-strategies.js +174 -0
  11. package/dist/orchestrator/prompt-benchmark.js +102 -0
  12. package/dist/orchestrator/prompt-optimizer.js +169 -0
  13. package/dist/orchestrator/refactor-scanner.js +222 -0
  14. package/dist/orchestrator/research-manager.js +104 -0
  15. package/dist/orchestrator/rulez.js +135 -0
  16. package/dist/orchestrator/sahoo-gateway.js +261 -0
  17. package/dist/orchestrator/state-manager.js +121 -0
  18. package/dist/orchestrator/task-agent.js +444 -0
  19. package/dist/orchestrator/telegram-bot.js +374 -0
  20. package/dist/orchestrator/types.js +2 -0
  21. package/dist/tests/dynamic/dependencies.test.js +37 -0
  22. package/dist/tests/dynamic/dummy.test.js +7 -0
  23. package/dist/tests/dynamic/fuzzy-patch.test.js +68 -0
  24. package/dist/tests/dynamic/indexer.test.js +60 -0
  25. package/dist/tests/dynamic/openhands.test.js +83 -0
  26. package/dist/tests/dynamic/skills.test.js +88 -0
  27. package/dist/tests/run-tests.js +294 -0
  28. package/dist/tools/diff-tools.js +24 -0
  29. package/dist/tools/file-tools.js +191 -0
  30. package/dist/tools/indexer.js +301 -0
  31. package/dist/tools/math-helper.js +6 -0
  32. package/dist/tools/repo-map.js +122 -0
  33. package/dist/tools/search-tools.js +271 -0
  34. package/dist/tools/shell-tools.js +75 -0
  35. package/dist/tools/skills.js +122 -0
  36. package/dist/tools/tui-tools.js +82 -0
  37. package/docs/AI_Arch_Opt_Anti_Gaming.md +227 -0
  38. package/docs/AI_Self_Improvement_Safety.md +457 -0
  39. package/docs/Anthropic AI Agents_ Capabilities and Concerns.md +134 -0
  40. package/docs/Auto_ClosedLoop_AI_Agent.md +415 -0
  41. package/docs/Autonomous AI Agents_ Closing the Loop.docx +0 -0
  42. package/docs/Secure_AI_Sandbox_Framework.md +358 -0
  43. package/docs/skills/add-file-existence-check-utility.json +9 -0
  44. package/docs/skills/add-utility-function-for-file-existence-check.json +9 -0
  45. package/docs/skills/add-utility-function-to-module.json +9 -0
  46. package/docs/skills/extract-command-runner-utility.json +9 -0
  47. package/docs/skills/file-existence-check-utility.json +9 -0
  48. package/package.json +36 -0
  49. package/src/dashboard/public/index.css +1334 -0
  50. package/src/dashboard/public/index.html +385 -0
  51. package/src/dashboard/public/index.js +1059 -0
  52. package/src/dashboard/server.ts +209 -0
  53. package/src/index.ts +256 -0
  54. package/src/orchestrator/agent-prompts.ts +43 -0
  55. package/src/orchestrator/autogenesis.ts +1078 -0
  56. package/src/orchestrator/dgm-archive.ts +257 -0
  57. package/src/orchestrator/event-stream.ts +90 -0
  58. package/src/orchestrator/fitness-evaluator.ts +154 -0
  59. package/src/orchestrator/meta-agent.ts +434 -0
  60. package/src/orchestrator/microagent-registry.ts +115 -0
  61. package/src/orchestrator/microagents/git-helper.md +11 -0
  62. package/src/orchestrator/microagents/test-fixer.md +10 -0
  63. package/src/orchestrator/microagents/typescript-expert.md +11 -0
  64. package/src/orchestrator/mutation-strategies.ts +214 -0
  65. package/src/orchestrator/research-manager.ts +88 -0
  66. package/src/orchestrator/rulez.ts +118 -0
  67. package/src/orchestrator/sahoo-gateway.ts +300 -0
  68. package/src/orchestrator/state-manager.ts +161 -0
  69. package/src/orchestrator/system-prompt.txt +1 -0
  70. package/src/orchestrator/task-agent.ts +461 -0
  71. package/src/orchestrator/telegram-bot.ts +358 -0
  72. package/src/tests/dynamic/dependencies.test.ts +48 -0
  73. package/src/tests/dynamic/dummy.test.ts +4 -0
  74. package/src/tests/dynamic/fuzzy-patch.test.ts +42 -0
  75. package/src/tests/dynamic/indexer.test.ts +31 -0
  76. package/src/tests/dynamic/openhands.test.ts +59 -0
  77. package/src/tests/dynamic/skills.test.ts +63 -0
  78. package/src/tests/run-tests.ts +296 -0
  79. package/src/tools/diff-tools.ts +27 -0
  80. package/src/tools/file-tools.ts +187 -0
  81. package/src/tools/indexer.ts +325 -0
  82. package/src/tools/repo-map.ts +96 -0
  83. package/src/tools/search-tools.ts +258 -0
  84. package/src/tools/shell-tools.ts +90 -0
  85. package/src/tools/skills.ts +101 -0
  86. package/src/tools/tui-tools.ts +87 -0
@@ -0,0 +1,358 @@
1
+ import * as https from 'https';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { spawn } from 'child_process';
5
+ import { getEvolutionState } from './state-manager';
6
+
7
+ async function callTelegramApi(token: string, method: string, payload: any): Promise<any> {
8
+ return new Promise((resolve, reject) => {
9
+ const url = `https://api.telegram.org/bot${token}/${method}`;
10
+ const data = JSON.stringify(payload);
11
+ const req = https.request(url, {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ 'Content-Length': Buffer.byteLength(data),
16
+ },
17
+ }, (res) => {
18
+ let body = '';
19
+ res.on('data', chunk => body += chunk);
20
+ res.on('end', () => {
21
+ try {
22
+ const response = JSON.parse(body);
23
+ if (response.ok) {
24
+ resolve(response.result);
25
+ } else {
26
+ reject(new Error(`Telegram API Error: ${response.description}`));
27
+ }
28
+ } catch (e) {
29
+ reject(e);
30
+ }
31
+ });
32
+ });
33
+ req.on('error', reject);
34
+ req.write(data);
35
+ req.end();
36
+ });
37
+ }
38
+
39
+ let runningProcess: any = null;
40
+
41
+ async function handleTelegramCommand(token: string, message: any, activeChats: Set<number>) {
42
+ const chatId = message.chat.id;
43
+ const text = message.text?.trim() || '';
44
+
45
+ if (text.startsWith('/start') || text.startsWith('/help')) {
46
+ await callTelegramApi(token, 'sendMessage', {
47
+ chat_id: chatId,
48
+ text: `🤖 *Closed-Loop Agent ChatOps Console*\n\n` +
49
+ `*Commands:*\n` +
50
+ `/status - View metrics, cycles, token budget, and active task status\n` +
51
+ `/logs - View last 25 log lines of evolution.log\n` +
52
+ `/refactor <goal> - Run autonomous refactoring for goal\n` +
53
+ `/task <task> - Run custom evolution step for task\n` +
54
+ `/kill - Cancel any running task spawned by the bot`,
55
+ parse_mode: 'Markdown'
56
+ });
57
+ return;
58
+ }
59
+
60
+ if (text.startsWith('/status')) {
61
+ const state = getEvolutionState();
62
+ const statusText = `📊 *Current Agent Status: ${state.status.toUpperCase()}*\n\n` +
63
+ `- *Cycle*: ${state.currentCycle} / ${state.maxCycles === 0 ? 'Infinite' : state.maxCycles}\n` +
64
+ `- *Token Budget*: ${state.tokenBudget} total\n` +
65
+ `- *Tokens Used*: ${state.tokensUsed.total} (Input: ${state.tokensUsed.input}, Output: ${state.tokensUsed.output})\n` +
66
+ `- *Last Updated*: ${state.lastUpdated}\n` +
67
+ `- *Current Task*: ${state.currentTask || 'None'}`;
68
+ await callTelegramApi(token, 'sendMessage', {
69
+ chat_id: chatId,
70
+ text: statusText,
71
+ parse_mode: 'Markdown'
72
+ });
73
+ return;
74
+ }
75
+
76
+ if (text.startsWith('/logs')) {
77
+ try {
78
+ const logPath = path.join(process.cwd(), 'evolution.log');
79
+ if (fs.existsSync(logPath)) {
80
+ const logContent = fs.readFileSync(logPath, 'utf-8');
81
+ const lines = logContent.split('\n');
82
+ const lastLines = lines.slice(-25).join('\n');
83
+ await callTelegramApi(token, 'sendMessage', {
84
+ chat_id: chatId,
85
+ text: `📝 *Last 25 log lines:*\n\`\`\`\n${lastLines || 'No logs yet.'}\n\`\`\``,
86
+ parse_mode: 'Markdown'
87
+ });
88
+ } else {
89
+ await callTelegramApi(token, 'sendMessage', {
90
+ chat_id: chatId,
91
+ text: `❌ Log file 'evolution.log' does not exist yet.`
92
+ });
93
+ }
94
+ } catch (err: any) {
95
+ await callTelegramApi(token, 'sendMessage', {
96
+ chat_id: chatId,
97
+ text: `❌ Error reading log file: ${err.message}`
98
+ });
99
+ }
100
+ return;
101
+ }
102
+
103
+ if (text.startsWith('/kill')) {
104
+ if (runningProcess) {
105
+ runningProcess.kill();
106
+ runningProcess = null;
107
+ await callTelegramApi(token, 'sendMessage', {
108
+ chat_id: chatId,
109
+ text: `🛑 Active evolution task was manually terminated.`
110
+ });
111
+ } else {
112
+ await callTelegramApi(token, 'sendMessage', {
113
+ chat_id: chatId,
114
+ text: `ℹ️ No active evolution task is running.`
115
+ });
116
+ }
117
+ return;
118
+ }
119
+
120
+ const isRefactor = text.startsWith('/refactor ');
121
+ const isTask = text.startsWith('/task ');
122
+
123
+ if (isRefactor || isTask) {
124
+ if (runningProcess) {
125
+ await callTelegramApi(token, 'sendMessage', {
126
+ chat_id: chatId,
127
+ text: `⚠️ An evolution task is already running. Use /kill to stop it first.`
128
+ });
129
+ return;
130
+ }
131
+
132
+ const commandArg = isRefactor ? text.substring(10).trim() : text.substring(6).trim();
133
+ if (!commandArg) {
134
+ await callTelegramApi(token, 'sendMessage', {
135
+ chat_id: chatId,
136
+ text: `❌ Command argument cannot be empty.`
137
+ });
138
+ return;
139
+ }
140
+
141
+ // Run the agent process
142
+ await callTelegramApi(token, 'sendMessage', {
143
+ chat_id: chatId,
144
+ text: `🚀 Starting task: "${commandArg}"\nWaiting for initialization...`
145
+ });
146
+
147
+ let currentStage = 'reflecting';
148
+ let outputBuffer = '';
149
+ let lastSendTime = Date.now();
150
+
151
+ const sendBufferedOutput = async () => {
152
+ if (!outputBuffer.trim()) return;
153
+ const cleanOutput = outputBuffer.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
154
+ outputBuffer = '';
155
+ lastSendTime = Date.now();
156
+
157
+ const chunk = cleanOutput.length > 3900 ? cleanOutput.substring(cleanOutput.length - 3900) : cleanOutput;
158
+
159
+ try {
160
+ await callTelegramApi(token, 'sendMessage', {
161
+ chat_id: chatId,
162
+ text: `💻 *Process Logs:*\n\`\`\`\n${chunk}\n\`\`\``,
163
+ parse_mode: 'Markdown'
164
+ });
165
+ } catch (err: any) {
166
+ console.error('[Telegram Bot] Failed to send log chunk:', err.message);
167
+ }
168
+ };
169
+
170
+ const handleData = async (data: string) => {
171
+ outputBuffer += data;
172
+
173
+ let stageChanged = false;
174
+ let newStage = '';
175
+ let stageEmoji = '';
176
+ let stageLabel = '';
177
+
178
+ if (data.includes('AGP - Reflect') && currentStage !== 'reflecting') {
179
+ newStage = 'reflecting';
180
+ stageEmoji = '🔄';
181
+ stageLabel = 'Reflecting (Diagnosis)';
182
+ stageChanged = true;
183
+ } else if (data.includes('AGP - Scan') && currentStage !== 'scanning') {
184
+ newStage = 'scanning';
185
+ stageEmoji = '🔍';
186
+ stageLabel = 'Scanning Codebase';
187
+ stageChanged = true;
188
+ } else if ((data.includes('improve') || data.includes('Self-Modification Loop') || data.includes('TaskAgent')) && currentStage !== 'improving') {
189
+ newStage = 'improving';
190
+ stageEmoji = '🛠️';
191
+ stageLabel = 'Refactoring / Implementing';
192
+ stageChanged = true;
193
+ } else if ((data.includes('AGP - Evaluate') || data.includes('evaluate') || data.includes('SAHOO')) && currentStage !== 'evaluating') {
194
+ newStage = 'evaluating';
195
+ stageEmoji = '🧪';
196
+ stageLabel = 'Evaluating & SAHOO Verification';
197
+ stageChanged = true;
198
+ } else if (data.includes('AGP - Commit') && currentStage !== 'committing') {
199
+ newStage = 'committing';
200
+ stageEmoji = '💾';
201
+ stageLabel = 'Committing Changes';
202
+ stageChanged = true;
203
+ } else if (data.includes('AGP - Rollback') && currentStage !== 'rolling_back') {
204
+ newStage = 'rolling_back';
205
+ stageEmoji = '⚠️';
206
+ stageLabel = 'Rolling Back Mutations';
207
+ stageChanged = true;
208
+ }
209
+
210
+ if (stageChanged) {
211
+ currentStage = newStage;
212
+ await sendBufferedOutput();
213
+ await callTelegramApi(token, 'sendMessage', {
214
+ chat_id: chatId,
215
+ text: `${stageEmoji} *Evolution Stage*: ${stageLabel}...`,
216
+ parse_mode: 'Markdown'
217
+ });
218
+ } else if (Date.now() - lastSendTime > 4000) {
219
+ await sendBufferedOutput();
220
+ }
221
+ };
222
+
223
+ const processArgs = ['dist/index.js'];
224
+ if (isRefactor) {
225
+ processArgs.push(`Refactor ${commandArg}`);
226
+ } else {
227
+ processArgs.push(commandArg);
228
+ }
229
+
230
+ const child = spawn('node', processArgs, {
231
+ cwd: process.cwd(),
232
+ env: { ...process.env, FORCE_COLOR: '0' }
233
+ });
234
+
235
+ runningProcess = child;
236
+
237
+ child.stdout.on('data', (chunk) => {
238
+ handleData(chunk.toString());
239
+ });
240
+
241
+ child.stderr.on('data', (chunk) => {
242
+ handleData(chunk.toString());
243
+ });
244
+
245
+ child.on('close', async (code) => {
246
+ runningProcess = null;
247
+ await sendBufferedOutput();
248
+
249
+ if (code === 0) {
250
+ await callTelegramApi(token, 'sendMessage', {
251
+ chat_id: chatId,
252
+ text: `✅ *Evolution Step Completed Successfully!* (Exit Code: ${code})`,
253
+ parse_mode: 'Markdown'
254
+ });
255
+ } else {
256
+ await callTelegramApi(token, 'sendMessage', {
257
+ chat_id: chatId,
258
+ text: `❌ *Evolution Step Failed / Aborted!* (Exit Code: ${code})`,
259
+ parse_mode: 'Markdown'
260
+ });
261
+ }
262
+ });
263
+
264
+ child.on('error', async (err) => {
265
+ runningProcess = null;
266
+ await callTelegramApi(token, 'sendMessage', {
267
+ chat_id: chatId,
268
+ text: `❌ *Subprocess Error:* ${err.message}`,
269
+ parse_mode: 'Markdown'
270
+ });
271
+ });
272
+ }
273
+ }
274
+
275
+ export async function startTelegramBot() {
276
+ const token = process.env.TELEGRAM_BOT_TOKEN;
277
+ if (!token) {
278
+ console.log('\x1b[33m[Telegram Bot] Warning: TELEGRAM_BOT_TOKEN is not set in .env. Telegram ChatOps will be inactive.\x1b[0m');
279
+ return;
280
+ }
281
+
282
+ const allowedUserIdsStr = process.env.TELEGRAM_ALLOWED_USER_IDS || '';
283
+ const allowedUserIds = allowedUserIdsStr.split(',').map(s => s.trim()).filter(Boolean);
284
+
285
+ console.log(`\x1b[32m[Telegram Bot] Initializing Telegram ChatOps... Allowed User IDs: [${allowedUserIds.join(', ')}]\x1b[0m`);
286
+
287
+ let lastUpdateId = 0;
288
+ const activeChats = new Set<number>();
289
+
290
+ // Start polling loop
291
+ (async () => {
292
+ while (true) {
293
+ try {
294
+ const updates = await callTelegramApi(token, 'getUpdates', {
295
+ offset: lastUpdateId + 1,
296
+ timeout: 20,
297
+ });
298
+
299
+ for (const update of updates) {
300
+ lastUpdateId = Math.max(lastUpdateId, update.update_id);
301
+ if (update.message) {
302
+ const chatId = update.message.chat.id;
303
+ const fromId = update.message.from?.id;
304
+ const fromIdStr = fromId ? String(fromId) : '';
305
+
306
+ if (!allowedUserIds.includes(fromIdStr)) {
307
+ console.log(`[Telegram Bot] Blocked unauthorized message from user ID: ${fromIdStr}`);
308
+ await callTelegramApi(token, 'sendMessage', {
309
+ chat_id: chatId,
310
+ text: `❌ *Unauthorized*\nYour User ID (${fromIdStr}) is not in the allowed list.`,
311
+ parse_mode: 'Markdown'
312
+ });
313
+ continue;
314
+ }
315
+
316
+ activeChats.add(chatId);
317
+ await handleTelegramCommand(token, update.message, activeChats);
318
+ }
319
+ }
320
+ } catch (err: any) {
321
+ console.error('[Telegram Bot] Error polling updates:', err.message);
322
+ await new Promise(resolve => setTimeout(resolve, 5000));
323
+ }
324
+ }
325
+ })();
326
+
327
+ // Start state monitoring / progress broadcasting
328
+ let lastStateStr = '';
329
+ setInterval(() => {
330
+ try {
331
+ const state = getEvolutionState();
332
+ const stateStr = JSON.stringify({
333
+ status: state.status,
334
+ currentCycle: state.currentCycle,
335
+ tokensUsed: state.tokensUsed.total
336
+ });
337
+
338
+ if (lastStateStr && lastStateStr !== stateStr && activeChats.size > 0) {
339
+ const broadcastText = `📢 *Evolution State Update*\n\n` +
340
+ `- *Status*: ${state.status.toUpperCase()}\n` +
341
+ `- *Cycle*: ${state.currentCycle}\n` +
342
+ `- *Tokens Used*: ${state.tokensUsed.total}`;
343
+ for (const chatId of activeChats) {
344
+ callTelegramApi(token, 'sendMessage', {
345
+ chat_id: chatId,
346
+ text: broadcastText,
347
+ parse_mode: 'Markdown'
348
+ }).catch(err => {
349
+ console.error(`[Telegram Bot] Failed to broadcast state to chat ${chatId}:`, err.message);
350
+ });
351
+ }
352
+ }
353
+ lastStateStr = stateStr;
354
+ } catch (e) {
355
+ // ignore
356
+ }
357
+ }, 5000);
358
+ }
@@ -0,0 +1,48 @@
1
+ import { generateCodeIndex, getDependents } from '../../tools/indexer';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ export async function run() {
6
+ console.log(" --> Running Dependency Graph tests...");
7
+
8
+ const workspaceRoot = process.cwd();
9
+
10
+ // 1. Rebuild Index
11
+ const index = generateCodeIndex(workspaceRoot);
12
+ if (index.length === 0) {
13
+ throw new Error("Failed to index files!");
14
+ }
15
+
16
+ // Find a file to test dependencies on, e.g. shell-tools.ts
17
+ const shellToolsFile = 'src/tools/shell-tools.ts';
18
+ const dependents = getDependents(shellToolsFile, workspaceRoot);
19
+
20
+ console.log(` --> Dependents of "${shellToolsFile}":\n`, dependents);
21
+
22
+ if (dependents.length === 0) {
23
+ throw new Error(`Expected at least one file to import "${shellToolsFile}" but found none!`);
24
+ }
25
+
26
+ // Check that key importing files are in the list
27
+ const hasTaskAgent = dependents.some(d => d.includes('task-agent.ts'));
28
+ const hasTests = dependents.some(d => d.includes('run-tests.ts'));
29
+
30
+ if (!hasTaskAgent && !hasTests) {
31
+ throw new Error(`Dependency check missed expected importers like task-agent.ts or run-tests.ts!`);
32
+ }
33
+
34
+ // 2. Check imports extraction
35
+ const taskAgentIndex = index.find(f => f.filePath === 'src/orchestrator/task-agent.ts');
36
+ if (!taskAgentIndex) {
37
+ throw new Error("Failed to find task-agent.ts index entry!");
38
+ }
39
+
40
+ console.log(" --> Imports of task-agent.ts:\n", taskAgentIndex.imports);
41
+
42
+ const importsShellTools = taskAgentIndex.imports.some(imp => imp.includes('shell-tools'));
43
+ if (!importsShellTools) {
44
+ throw new Error("Failed to extract shell-tools import from task-agent.ts!");
45
+ }
46
+
47
+ console.log(" --> Dependency Graph tests passed!");
48
+ }
@@ -0,0 +1,4 @@
1
+ // Dummy dynamic test file
2
+ export async function run() {
3
+ console.log(" --> Dynamic dummy test execution successful!");
4
+ }
@@ -0,0 +1,42 @@
1
+ import { editFile, writeFile, readFile } from '../../tools/file-tools';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ export async function run() {
6
+ console.log(" --> Running Fuzzy Patch tests...");
7
+
8
+ const testFile = 'temp-fuzzy-test.ts';
9
+
10
+ // Write source code with dynamic formatting
11
+ const originalCode = `function calculateTotal(a: number, b: number) {
12
+ const sum = a + b;
13
+ return sum;
14
+ }`;
15
+
16
+ writeFile(testFile, originalCode);
17
+
18
+ // Attempt fuzzy edit: target has different spaces and newlines
19
+ const targetWithDifferentSpacing = `const sum = a + b;
20
+ return sum;`;
21
+
22
+ const replacement = `const sum = a + b;
23
+ console.log("Adding:", a, b);
24
+ return sum;`;
25
+
26
+ try {
27
+ editFile(testFile, targetWithDifferentSpacing, replacement);
28
+
29
+ const resultingCode = readFile(testFile);
30
+ console.log(" --> Output content after fuzzy edit:\n", resultingCode);
31
+
32
+ if (!resultingCode.includes('console.log("Adding:", a, b)')) {
33
+ throw new Error("Fuzzy patch was not applied successfully!");
34
+ }
35
+
36
+ console.log(" --> Fuzzy Patch tests passed!");
37
+ } finally {
38
+ if (fs.existsSync(testFile)) {
39
+ fs.unlinkSync(testFile);
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,31 @@
1
+ import { generateCodeIndex, queryCodeIndex } from '../../tools/indexer';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ export async function run() {
6
+ console.log(" --> Running indexer tests...");
7
+
8
+ // 1. Generate code index
9
+ const workspaceRoot = process.cwd();
10
+ const index = generateCodeIndex(workspaceRoot);
11
+
12
+ if (index.length === 0) {
13
+ throw new Error("Indexed file list should not be empty!");
14
+ }
15
+
16
+ // Verify code-index.json exists
17
+ const indexPath = path.join(workspaceRoot, 'code-index.json');
18
+ if (!fs.existsSync(indexPath)) {
19
+ throw new Error("code-index.json file was not created!");
20
+ }
21
+
22
+ // 2. Query index
23
+ const queryResult = queryCodeIndex("AutogenesisEngine");
24
+ console.log(" --> Query search 'AutogenesisEngine' result:\n", queryResult);
25
+
26
+ if (!queryResult.includes("AutogenesisEngine")) {
27
+ throw new Error("Indexer query failed to find 'AutogenesisEngine'");
28
+ }
29
+
30
+ console.log(" --> Indexer tests passed!");
31
+ }
@@ -0,0 +1,59 @@
1
+ import { EventStream } from '../../orchestrator/event-stream';
2
+ import { MicroagentRegistry } from '../../orchestrator/microagent-registry';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+
6
+ export async function run() {
7
+ console.log(" --> Running OpenHands (EventStream & Microagents) tests...");
8
+
9
+ // 1. Test EventStream
10
+ const stream = EventStream.getInstance();
11
+ stream.clear();
12
+
13
+ stream.publish('user', 'message', 'TestUserQuery', 'Hello agent');
14
+ stream.publish('agent', 'action', 'readFile', '{"path": "package.json"}');
15
+ stream.publish('environment', 'observation', 'readFileObservation', 'File content here');
16
+
17
+ const events = stream.getEvents();
18
+ if (events.length !== 3) {
19
+ throw new Error(`Expected 3 events in EventStream, got ${events.length}`);
20
+ }
21
+
22
+ if (events[0].source !== 'user' || events[0].name !== 'TestUserQuery') {
23
+ throw new Error(`EventStream mismatch on user query: ${JSON.stringify(events[0])}`);
24
+ }
25
+
26
+ if (events[1].source !== 'agent' || events[1].type !== 'action') {
27
+ throw new Error(`EventStream mismatch on agent action: ${JSON.stringify(events[1])}`);
28
+ }
29
+
30
+ // Verify file was written
31
+ const historyPath = path.join(process.cwd(), 'event-stream-history.json');
32
+ if (!fs.existsSync(historyPath)) {
33
+ throw new Error('event-stream-history.json was not created');
34
+ }
35
+
36
+ // 2. Test MicroagentRegistry
37
+ const registry = MicroagentRegistry.getInstance();
38
+ registry.loadMicroagents();
39
+ const allAgents = registry.getMicroagents();
40
+
41
+ if (allAgents.length === 0) {
42
+ throw new Error('MicroagentRegistry failed to load microagents');
43
+ }
44
+
45
+ // Test triggers
46
+ const gitMatches = registry.findMatchingMicroagents('Please commit the changes and push');
47
+ const hasGitHelper = gitMatches.some(a => a.name === 'Git Helper');
48
+ if (!hasGitHelper) {
49
+ throw new Error('Expected Git Helper to match query containing "commit"');
50
+ }
51
+
52
+ const tsMatches = registry.findMatchingMicroagents('Refactor using typescript helper');
53
+ const hasTsExpert = tsMatches.some(a => a.name === 'TypeScript Expert');
54
+ if (!hasTsExpert) {
55
+ throw new Error('Expected TypeScript Expert to match query containing "typescript"');
56
+ }
57
+
58
+ console.log(" --> OpenHands (EventStream & Microagents) tests passed successfully!");
59
+ }
@@ -0,0 +1,63 @@
1
+ import { extractAndSaveSkill, getSkillsSummary } from '../../tools/skills';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ export async function run() {
6
+ console.log(" --> Running Skill Registry tests...");
7
+
8
+ const workspaceRoot = process.cwd();
9
+ const skillsDir = path.join(workspaceRoot, 'docs', 'skills');
10
+ const testSkillFile = path.join(skillsDir, 'test-reusable-skill.json');
11
+
12
+ // Clean up previous test files if any
13
+ if (fs.existsSync(testSkillFile)) {
14
+ fs.unlinkSync(testSkillFile);
15
+ }
16
+
17
+ // 1. Mock inputs
18
+ const mockTask = "Add a checkFileExists utility inside file-tools.ts";
19
+ const mockDiff = `diff --git a/src/tools/file-tools.ts b/src/tools/file-tools.ts
20
+ index e69de29..b2a8d11 100644
21
+ --- a/src/tools/file-tools.ts
22
+ +++ b/src/tools/file-tools.ts
23
+ @@ -1 +1,5 @@
24
+ +export function checkFileExists(path: string): boolean {
25
+ + return fs.existsSync(path);
26
+ +}
27
+ `;
28
+
29
+ // 2. Extract and Save
30
+ await extractAndSaveSkill(mockTask, mockDiff);
31
+
32
+ // Find any generated json files
33
+ if (!fs.existsSync(skillsDir)) {
34
+ throw new Error("skills directory was not created!");
35
+ }
36
+
37
+ const files = fs.readdirSync(skillsDir).filter(f => f.endsWith('.json'));
38
+ if (files.length === 0) {
39
+ throw new Error("No playbooks were written by extractAndSaveSkill!");
40
+ }
41
+
42
+ console.log(` --> Created playbooks: ${files.join(', ')}`);
43
+
44
+ // 3. Summarize
45
+ const summaries = getSkillsSummary();
46
+ console.log(" --> Playbook summaries:\n", summaries);
47
+
48
+ if (summaries === 'No custom development skills recorded yet.') {
49
+ throw new Error("getSkillsSummary returned default empty statement despite files present.");
50
+ }
51
+
52
+ // Clean up mock output to keep it clean, but wait, keeping it is also fine since git ignores or tracks.
53
+ // Actually, let's keep one test playbook for verification and delete any temporary ones if needed.
54
+ // Let's delete the temporary test file.
55
+ const testFiles = files.filter(f => f.includes('reusable-skill') || f.includes('exists'));
56
+ for (const f of testFiles) {
57
+ try {
58
+ fs.unlinkSync(path.join(skillsDir, f));
59
+ } catch(e) {}
60
+ }
61
+
62
+ console.log(" --> Skill Registry tests passed!");
63
+ }