@sschepis/robodev 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai.mjs +8 -0
- package/package.json +48 -0
- package/src/cli/cli-interface.mjs +271 -0
- package/src/config.mjs +64 -0
- package/src/core/ai-assistant.mjs +540 -0
- package/src/core/ai-provider.mjs +579 -0
- package/src/core/history-manager.mjs +330 -0
- package/src/core/system-prompt.mjs +182 -0
- package/src/custom-tools/custom-tools-manager.mjs +310 -0
- package/src/execution/tool-executor.mjs +892 -0
- package/src/lib/README.md +114 -0
- package/src/lib/adapters/console-status-adapter.mjs +48 -0
- package/src/lib/adapters/network-llm-adapter.mjs +37 -0
- package/src/lib/index.mjs +101 -0
- package/src/lib/interfaces.d.ts +98 -0
- package/src/main.mjs +61 -0
- package/src/package/package-manager.mjs +223 -0
- package/src/quality/code-validator.mjs +126 -0
- package/src/quality/quality-evaluator.mjs +248 -0
- package/src/reasoning/reasoning-system.mjs +258 -0
- package/src/structured-dev/flow-manager.mjs +321 -0
- package/src/structured-dev/implementation-planner.mjs +223 -0
- package/src/structured-dev/manifest-manager.mjs +423 -0
- package/src/structured-dev/plan-executor.mjs +113 -0
- package/src/structured-dev/project-bootstrapper.mjs +523 -0
- package/src/tools/desktop-automation-tools.mjs +172 -0
- package/src/tools/file-tools.mjs +141 -0
- package/src/tools/tool-definitions.mjs +872 -0
- package/src/ui/console-styler.mjs +503 -0
- package/src/workspace/workspace-manager.mjs +215 -0
- package/themes.json +66 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// Custom tools management system
|
|
2
|
+
// Handles loading, saving, validating, and managing custom tools
|
|
3
|
+
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { consoleStyler } from '../ui/console-styler.mjs';
|
|
7
|
+
|
|
8
|
+
export class CustomToolsManager {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.customTools = new Map(); // Map of tool_name -> function
|
|
11
|
+
this.customToolSchemas = new Map(); // Map of tool_name -> schema
|
|
12
|
+
this.toolsFilePath = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../.tools.json');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Load custom tools from .tools.json on startup
|
|
16
|
+
async loadCustomTools() {
|
|
17
|
+
try {
|
|
18
|
+
const fs = await import('fs');
|
|
19
|
+
if (!fs.existsSync(this.toolsFilePath)) {
|
|
20
|
+
consoleStyler.log('tools', `No custom tools file found at ${this.toolsFilePath}`);
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const toolsData = JSON.parse(fs.readFileSync(this.toolsFilePath, 'utf8'));
|
|
25
|
+
let loadedCount = 0;
|
|
26
|
+
const loadedSchemas = [];
|
|
27
|
+
|
|
28
|
+
for (const [toolName, toolDef] of Object.entries(toolsData.tools || {})) {
|
|
29
|
+
try {
|
|
30
|
+
if (this.validateCustomTool(toolDef)) {
|
|
31
|
+
// Convert function string back to executable function
|
|
32
|
+
const toolFunction = new Function('return ' + toolDef.function)();
|
|
33
|
+
|
|
34
|
+
// Add to our maps
|
|
35
|
+
this.customTools.set(toolName, toolFunction);
|
|
36
|
+
this.customToolSchemas.set(toolName, toolDef.schema);
|
|
37
|
+
|
|
38
|
+
// Add schema to return array
|
|
39
|
+
loadedSchemas.push(toolDef.schema);
|
|
40
|
+
|
|
41
|
+
loadedCount++;
|
|
42
|
+
consoleStyler.log('tools', `✓ Loaded custom tool: ${toolName}`);
|
|
43
|
+
} else {
|
|
44
|
+
consoleStyler.log('warning', `✗ Invalid tool: ${toolName}`);
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
consoleStyler.log('error', `✗ Failed to load tool ${toolName}: ${error.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (loadedCount > 0) {
|
|
52
|
+
consoleStyler.log('tools', `Loaded ${loadedCount} custom tools`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return loadedSchemas;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
consoleStyler.log('warning', `Failed to load custom tools: ${error.message}`);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate a custom tool before loading
|
|
63
|
+
validateCustomTool(toolDef) {
|
|
64
|
+
// Check required fields
|
|
65
|
+
if (!toolDef.schema || !toolDef.function) return false;
|
|
66
|
+
|
|
67
|
+
// Validate schema structure
|
|
68
|
+
if (!toolDef.schema.function || !toolDef.schema.function.name) return false;
|
|
69
|
+
|
|
70
|
+
// Basic security checks - block dangerous patterns
|
|
71
|
+
const dangerousPatterns = [
|
|
72
|
+
/process\.exit\s*\(/,
|
|
73
|
+
/require\s*\(\s*['"]child_process['"]\s*\)/,
|
|
74
|
+
/import\s*\(\s*['"]child_process['"]\s*\)/,
|
|
75
|
+
/eval\s*\(/,
|
|
76
|
+
/Function\s*\(/,
|
|
77
|
+
/fs\.rmSync/,
|
|
78
|
+
/fs\.unlinkSync.*\.\./, // Prevent path traversal deletion
|
|
79
|
+
/rm\s+-rf/,
|
|
80
|
+
/format\s+c:/i
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
for (const pattern of dangerousPatterns) {
|
|
84
|
+
if (pattern.test(toolDef.function)) {
|
|
85
|
+
consoleStyler.log('error', `SECURITY: Rejected tool with dangerous pattern: ${pattern}`);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Save a custom tool to .tools.json
|
|
94
|
+
async saveCustomTool(toolName, toolFunction, toolSchema, category = 'utility') {
|
|
95
|
+
try {
|
|
96
|
+
const fs = await import('fs');
|
|
97
|
+
|
|
98
|
+
// Load existing tools or create new structure
|
|
99
|
+
let toolsData = { version: '1.0.0', tools: {} };
|
|
100
|
+
if (fs.existsSync(this.toolsFilePath)) {
|
|
101
|
+
try {
|
|
102
|
+
toolsData = JSON.parse(fs.readFileSync(this.toolsFilePath, 'utf8'));
|
|
103
|
+
} catch (e) {
|
|
104
|
+
consoleStyler.log('warning', 'Corrupted tools file, creating new one');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Add the new tool
|
|
109
|
+
toolsData.tools[toolName] = {
|
|
110
|
+
schema: toolSchema,
|
|
111
|
+
function: toolFunction.toString(),
|
|
112
|
+
metadata: {
|
|
113
|
+
created_at: new Date().toISOString(),
|
|
114
|
+
category: category,
|
|
115
|
+
usage_count: 0,
|
|
116
|
+
last_used: null
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Create backup first
|
|
121
|
+
if (fs.existsSync(this.toolsFilePath)) {
|
|
122
|
+
const backupPath = this.toolsFilePath.replace('.json', '.backup.json');
|
|
123
|
+
fs.copyFileSync(this.toolsFilePath, backupPath);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Save the updated tools
|
|
127
|
+
fs.writeFileSync(this.toolsFilePath, JSON.stringify(toolsData, null, 2), 'utf8');
|
|
128
|
+
|
|
129
|
+
// Add to runtime maps
|
|
130
|
+
this.customTools.set(toolName, toolFunction);
|
|
131
|
+
this.customToolSchemas.set(toolName, toolSchema);
|
|
132
|
+
|
|
133
|
+
consoleStyler.log('tools', `✓ Saved custom tool: ${toolName}`);
|
|
134
|
+
return { success: true, schema: toolSchema };
|
|
135
|
+
} catch (error) {
|
|
136
|
+
consoleStyler.log('error', `✗ Failed to save tool ${toolName}: ${error.message}`);
|
|
137
|
+
return { success: false, error: error.message };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Update tool usage statistics
|
|
142
|
+
async updateToolUsage(toolName) {
|
|
143
|
+
try {
|
|
144
|
+
const fs = await import('fs');
|
|
145
|
+
if (!fs.existsSync(this.toolsFilePath)) return;
|
|
146
|
+
|
|
147
|
+
const toolsData = JSON.parse(fs.readFileSync(this.toolsFilePath, 'utf8'));
|
|
148
|
+
if (toolsData.tools && toolsData.tools[toolName]) {
|
|
149
|
+
toolsData.tools[toolName].metadata.usage_count = (toolsData.tools[toolName].metadata.usage_count || 0) + 1;
|
|
150
|
+
toolsData.tools[toolName].metadata.last_used = new Date().toISOString();
|
|
151
|
+
|
|
152
|
+
fs.writeFileSync(this.toolsFilePath, JSON.stringify(toolsData, null, 2), 'utf8');
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
// Ignore usage tracking errors
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// List custom tools with optional filtering
|
|
160
|
+
async listCustomTools(category = null, showUsage = false) {
|
|
161
|
+
try {
|
|
162
|
+
const fs = await import('fs');
|
|
163
|
+
|
|
164
|
+
if (!fs.existsSync(this.toolsFilePath)) {
|
|
165
|
+
return "No custom tools found.";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const toolsData = JSON.parse(fs.readFileSync(this.toolsFilePath, 'utf8'));
|
|
169
|
+
const tools = toolsData.tools || {};
|
|
170
|
+
|
|
171
|
+
let filteredTools = Object.entries(tools);
|
|
172
|
+
if (category) {
|
|
173
|
+
filteredTools = filteredTools.filter(([name, tool]) =>
|
|
174
|
+
tool.metadata.category === category
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (filteredTools.length === 0) {
|
|
179
|
+
return category ?
|
|
180
|
+
`No custom tools found in category: ${category}` :
|
|
181
|
+
"No custom tools found.";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let output = `Found ${filteredTools.length} custom tool(s):\n\n`;
|
|
185
|
+
|
|
186
|
+
for (const [name, tool] of filteredTools) {
|
|
187
|
+
output += `• **${name}** (${tool.metadata.category})\n`;
|
|
188
|
+
output += ` ${tool.schema.function.description}\n`;
|
|
189
|
+
|
|
190
|
+
if (showUsage) {
|
|
191
|
+
output += ` Used: ${tool.metadata.usage_count || 0} times\n`;
|
|
192
|
+
if (tool.metadata.last_used) {
|
|
193
|
+
output += ` Last used: ${new Date(tool.metadata.last_used).toLocaleString()}\n`;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
output += `\n`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return output.trim();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return `Error listing tools: ${error.message}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Remove a custom tool
|
|
206
|
+
async removeCustomTool(toolName) {
|
|
207
|
+
try {
|
|
208
|
+
const fs = await import('fs');
|
|
209
|
+
|
|
210
|
+
if (!fs.existsSync(this.toolsFilePath)) {
|
|
211
|
+
return { success: false, message: "No custom tools file found." };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const toolsData = JSON.parse(fs.readFileSync(this.toolsFilePath, 'utf8'));
|
|
215
|
+
|
|
216
|
+
if (!toolsData.tools || !toolsData.tools[toolName]) {
|
|
217
|
+
return { success: false, message: `Tool '${toolName}' not found.` };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Remove from file
|
|
221
|
+
delete toolsData.tools[toolName];
|
|
222
|
+
fs.writeFileSync(this.toolsFilePath, JSON.stringify(toolsData, null, 2), 'utf8');
|
|
223
|
+
|
|
224
|
+
// Remove from runtime
|
|
225
|
+
this.customTools.delete(toolName);
|
|
226
|
+
this.customToolSchemas.delete(toolName);
|
|
227
|
+
|
|
228
|
+
consoleStyler.log('tools', `✓ Removed custom tool: ${toolName}`);
|
|
229
|
+
return { success: true, message: `✓ Successfully removed tool: ${toolName}` };
|
|
230
|
+
} catch (error) {
|
|
231
|
+
return { success: false, message: `Error removing tool: ${error.message}` };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Export tools to a file
|
|
236
|
+
async exportTools(outputFile = 'exported_tools.json', toolsToExport = null) {
|
|
237
|
+
try {
|
|
238
|
+
const fs = await import('fs');
|
|
239
|
+
|
|
240
|
+
if (!fs.existsSync(this.toolsFilePath)) {
|
|
241
|
+
return { success: false, message: "No custom tools found to export." };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const toolsData = JSON.parse(fs.readFileSync(this.toolsFilePath, 'utf8'));
|
|
245
|
+
const allTools = toolsData.tools || {};
|
|
246
|
+
|
|
247
|
+
let exportData = {
|
|
248
|
+
version: toolsData.version || '1.0.0',
|
|
249
|
+
exported_at: new Date().toISOString(),
|
|
250
|
+
tools: {}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
if (toolsToExport && toolsToExport.length > 0) {
|
|
254
|
+
// Export specific tools
|
|
255
|
+
for (const toolName of toolsToExport) {
|
|
256
|
+
if (allTools[toolName]) {
|
|
257
|
+
exportData.tools[toolName] = allTools[toolName];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
// Export all tools
|
|
262
|
+
exportData.tools = allTools;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(outputFile, JSON.stringify(exportData, null, 2), 'utf8');
|
|
266
|
+
|
|
267
|
+
const toolCount = Object.keys(exportData.tools).length;
|
|
268
|
+
return {
|
|
269
|
+
success: true,
|
|
270
|
+
message: `✓ Successfully exported ${toolCount} tool(s) to: ${outputFile}`
|
|
271
|
+
};
|
|
272
|
+
} catch (error) {
|
|
273
|
+
return { success: false, message: `Error exporting tools: ${error.message}` };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check if a tool exists
|
|
278
|
+
hasCustomTool(toolName) {
|
|
279
|
+
return this.customTools.has(toolName);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Get a custom tool function
|
|
283
|
+
getCustomTool(toolName) {
|
|
284
|
+
return this.customTools.get(toolName);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Get all custom tool schemas
|
|
288
|
+
getCustomToolSchemas() {
|
|
289
|
+
return Array.from(this.customToolSchemas.values());
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Execute a custom tool
|
|
293
|
+
async executeCustomTool(toolName, args) {
|
|
294
|
+
if (!this.hasCustomTool(toolName)) {
|
|
295
|
+
throw new Error(`Custom tool '${toolName}' not found`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const toolFunction = this.getCustomTool(toolName);
|
|
299
|
+
consoleStyler.log('custom', `Executing custom tool: ${toolName}`);
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const result = await toolFunction(...Object.values(args));
|
|
303
|
+
await this.updateToolUsage(toolName);
|
|
304
|
+
return typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
consoleStyler.log('error', `Custom tool error: ${error.message}`);
|
|
307
|
+
throw new Error(`Custom tool error: ${error.message}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|