feat: wire 10 new tools — file_read, file_write, glob, grep, web_fetch, task CRUD, send_message, schedule_cron
- 10 new JS tool classes in src/tools/ (clean, no framework deps) - tools/index.js: registry-based init with env toggles - bot/index.js: 16 tool definitions + 16 handlers (was 4) - Added glob npm dependency - Tools: bash, file_edit, file_read, file_write, glob, grep, web_search, web_fetch, git, task_create/update/list, send_message, schedule_cron, delegate_agent, run_skill
This commit is contained in:
274
src/bot/index.js
274
src/bot/index.js
@@ -204,80 +204,135 @@ export async function initBot(config, api, tools, skills, agents) {
|
||||
const tools = [];
|
||||
const toolMap = svc.toolMap;
|
||||
|
||||
if (toolMap.has('bash')) {
|
||||
tools.push({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'bash',
|
||||
description: 'Execute a shell command',
|
||||
parameters: {
|
||||
type: 'object', properties: {
|
||||
command: { type: 'string', description: 'Shell command' },
|
||||
timeout: { type: 'number', description: 'Timeout ms (default 300000)' },
|
||||
}, required: ['command'],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (toolMap.has('web_search')) {
|
||||
tools.push({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'web_search',
|
||||
description: 'Search the web',
|
||||
parameters: {
|
||||
type: 'object', properties: {
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
num_results: { type: 'number', description: 'Results count (default 5)' },
|
||||
}, required: ['query'],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (toolMap.has('git')) {
|
||||
tools.push({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'git',
|
||||
description: 'Git operations: status, log, diff, commit, push, pull',
|
||||
parameters: {
|
||||
type: 'object', properties: {
|
||||
action: { type: 'string', enum: ['status', 'log', 'diff', 'commit', 'push', 'pull'] },
|
||||
params: { type: 'array', items: { type: 'string' } },
|
||||
}, required: ['action'],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (svc.agents.length) {
|
||||
tools.push({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'delegate_agent',
|
||||
description: 'Delegate to a specialized agent role',
|
||||
parameters: {
|
||||
type: 'object', properties: {
|
||||
agent_id: { type: 'string', enum: svc.agents.map(a => a.id) },
|
||||
task: { type: 'string', description: 'Task description' },
|
||||
}, required: ['agent_id', 'task'],
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
if (svc.skills.length) {
|
||||
tools.push({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'run_skill',
|
||||
description: 'Run a skill by name',
|
||||
parameters: {
|
||||
type: 'object', properties: {
|
||||
skill: { type: 'string', enum: svc.skills.map(s => s.name) },
|
||||
input: { type: 'string' },
|
||||
}, required: ['skill'],
|
||||
},
|
||||
},
|
||||
});
|
||||
// ── Tool definitions for the AI API (OpenAI function-calling format) ──
|
||||
const TOOL_DEFS = {
|
||||
bash: {
|
||||
description: 'Execute a shell command',
|
||||
parameters: { type: 'object', properties: {
|
||||
command: { type: 'string', description: 'Shell command to execute' },
|
||||
timeout: { type: 'number', description: 'Timeout in ms (default 300000)' },
|
||||
}, required: ['command'] },
|
||||
},
|
||||
file_edit: {
|
||||
description: 'Edit files — read, write, append, or find-and-replace',
|
||||
parameters: { type: 'object', properties: {
|
||||
action: { type: 'string', enum: ['read', 'write', 'append', 'edit'], description: 'Operation' },
|
||||
file_path: { type: 'string', description: 'File path' },
|
||||
content: { type: 'string', description: 'Content to write/append (for write/append)' },
|
||||
old_text: { type: 'string', description: 'Text to find (for edit)' },
|
||||
new_text: { type: 'string', description: 'Replacement text (for edit)' },
|
||||
}, required: ['action', 'file_path'] },
|
||||
},
|
||||
file_read: {
|
||||
description: 'Read file contents with line numbers and pagination',
|
||||
parameters: { type: 'object', properties: {
|
||||
file_path: { type: 'string', description: 'File path to read' },
|
||||
offset: { type: 'number', description: 'Start line (1-indexed, default 1)' },
|
||||
limit: { type: 'number', description: 'Max lines (default 500)' },
|
||||
}, required: ['file_path'] },
|
||||
},
|
||||
file_write: {
|
||||
description: 'Write content to a file (overwrites entire file)',
|
||||
parameters: { type: 'object', properties: {
|
||||
file_path: { type: 'string', description: 'File path' },
|
||||
content: { type: 'string', description: 'Content to write' },
|
||||
}, required: ['file_path', 'content'] },
|
||||
},
|
||||
glob: {
|
||||
description: 'Find files matching a glob pattern',
|
||||
parameters: { type: 'object', properties: {
|
||||
pattern: { type: 'string', description: 'Glob pattern (e.g. "**/*.js")' },
|
||||
cwd: { type: 'string', description: 'Working directory (default: current)' },
|
||||
}, required: ['pattern'] },
|
||||
},
|
||||
grep: {
|
||||
description: 'Search file contents using regex (ripgrep)',
|
||||
parameters: { type: 'object', properties: {
|
||||
pattern: { type: 'string', description: 'Regex pattern' },
|
||||
path: { type: 'string', description: 'Directory to search (default: .)' },
|
||||
file_glob: { type: 'string', description: 'File filter (e.g. "*.py")' },
|
||||
max_results: { type: 'number', description: 'Max matches (default 20)' },
|
||||
context: { type: 'number', description: 'Context lines before/after (default 0)' },
|
||||
}, required: ['pattern'] },
|
||||
},
|
||||
web_search: {
|
||||
description: 'Search the web for information',
|
||||
parameters: { type: 'object', properties: {
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
num_results: { type: 'number', description: 'Results count (default 5)' },
|
||||
}, required: ['query'] },
|
||||
},
|
||||
web_fetch: {
|
||||
description: 'Fetch content from a URL and return text',
|
||||
parameters: { type: 'object', properties: {
|
||||
url: { type: 'string', description: 'URL to fetch' },
|
||||
max_length: { type: 'number', description: 'Max chars to return (default 15000)' },
|
||||
}, required: ['url'] },
|
||||
},
|
||||
git: {
|
||||
description: 'Git operations: status, log, diff, commit, push, pull',
|
||||
parameters: { type: 'object', properties: {
|
||||
action: { type: 'string', enum: ['status', 'log', 'diff', 'commit', 'push', 'pull'] },
|
||||
params: { type: 'array', items: { type: 'string' } },
|
||||
}, required: ['action'] },
|
||||
},
|
||||
task_create: {
|
||||
description: 'Create a new task',
|
||||
parameters: { type: 'object', properties: {
|
||||
description: { type: 'string', description: 'Task description' },
|
||||
}, required: ['description'] },
|
||||
},
|
||||
task_update: {
|
||||
description: 'Update task status',
|
||||
parameters: { type: 'object', properties: {
|
||||
task_id: { type: 'string', description: 'Task ID' },
|
||||
status: { type: 'string', enum: ['pending', 'in_progress', 'completed', 'cancelled'] },
|
||||
}, required: ['task_id', 'status'] },
|
||||
},
|
||||
task_list: {
|
||||
description: 'List all tasks with status',
|
||||
parameters: { type: 'object', properties: {
|
||||
status: { type: 'string', description: 'Filter by status (optional)' },
|
||||
} },
|
||||
},
|
||||
send_message: {
|
||||
description: 'Send a message to Telegram chat or channel',
|
||||
parameters: { type: 'object', properties: {
|
||||
chat_id: { type: 'string', description: 'Target chat ID (optional, uses default)' },
|
||||
message: { type: 'string', description: 'Message text to send' },
|
||||
}, required: ['message'] },
|
||||
},
|
||||
schedule_cron: {
|
||||
description: 'Manage cron jobs (create/list/remove)',
|
||||
parameters: { type: 'object', properties: {
|
||||
action: { type: 'string', enum: ['create', 'list', 'remove'] },
|
||||
name: { type: 'string', description: 'Job name' },
|
||||
schedule: { type: 'string', description: 'Cron schedule (e.g. "0 9 * * *")' },
|
||||
command: { type: 'string', description: 'Command to run' },
|
||||
}, required: ['action'] },
|
||||
},
|
||||
delegate_agent: {
|
||||
description: 'Delegate to a specialized agent role',
|
||||
parameters: { type: 'object', properties: {
|
||||
agent_id: { type: 'string', enum: svc.agents.map(a => a.id) },
|
||||
task: { type: 'string', description: 'Task description' },
|
||||
}, required: ['agent_id', 'task'] },
|
||||
},
|
||||
run_skill: {
|
||||
description: 'Run a skill by name',
|
||||
parameters: { type: 'object', properties: {
|
||||
skill: { type: 'string', enum: svc.skills.map(s => s.name) },
|
||||
input: { type: 'string' },
|
||||
}, required: ['skill'] },
|
||||
},
|
||||
};
|
||||
|
||||
// Register all tools that have a matching class loaded
|
||||
for (const [name, def] of Object.entries(TOOL_DEFS)) {
|
||||
if (name === 'delegate_agent' && !svc.agents.length) continue;
|
||||
if (name === 'run_skill' && !svc.skills.length) continue;
|
||||
if (!toolMap.has(name) && name !== 'delegate_agent' && name !== 'run_skill') continue;
|
||||
tools.push({ type: 'function', function: { name, ...def } });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -402,6 +457,7 @@ export async function initBot(config, api, tools, skills, agents) {
|
||||
return fullResponse || '✅ Done.';
|
||||
}
|
||||
|
||||
// ── Tool handlers: route API tool_calls to tool class methods ──
|
||||
const toolHandlers = {
|
||||
bash: async (args) => {
|
||||
const tool = svc.toolMap.get('bash');
|
||||
@@ -414,6 +470,42 @@ export async function initBot(config, api, tools, skills, agents) {
|
||||
return `❌ Exit ${r.code}\n\`\`\`\n${err || out}\n\`\`\``;
|
||||
} catch (e) { return `❌ Bash error: ${e.message}`; }
|
||||
},
|
||||
file_edit: async (args) => {
|
||||
const tool = svc.toolMap.get('file_edit');
|
||||
if (!tool) return '❌ File edit tool unavailable.';
|
||||
try {
|
||||
const r = await tool[args.action](args.file_path, args.content, args.old_text, args.new_text);
|
||||
return typeof r === 'string' ? r : (r.success ? `✅ ${JSON.stringify(r)}` : `❌ ${r.error}`);
|
||||
} catch (e) { return `❌ File edit error: ${e.message}`; }
|
||||
},
|
||||
file_read: async (args) => {
|
||||
const tool = svc.toolMap.get('file_read');
|
||||
if (!tool) return '❌ File read tool unavailable.';
|
||||
try {
|
||||
return await tool.execute(args);
|
||||
} catch (e) { return `❌ File read error: ${e.message}`; }
|
||||
},
|
||||
file_write: async (args) => {
|
||||
const tool = svc.toolMap.get('file_write');
|
||||
if (!tool) return '❌ File write tool unavailable.';
|
||||
try {
|
||||
return await tool.execute(args);
|
||||
} catch (e) { return `❌ File write error: ${e.message}`; }
|
||||
},
|
||||
glob: async (args) => {
|
||||
const tool = svc.toolMap.get('glob');
|
||||
if (!tool) return '❌ Glob tool unavailable.';
|
||||
try {
|
||||
return await tool.execute(args);
|
||||
} catch (e) { return `❌ Glob error: ${e.message}`; }
|
||||
},
|
||||
grep: async (args) => {
|
||||
const tool = svc.toolMap.get('grep');
|
||||
if (!tool) return '❌ Grep tool unavailable.';
|
||||
try {
|
||||
return await tool.execute(args);
|
||||
} catch (e) { return `❌ Grep error: ${e.message}`; }
|
||||
},
|
||||
web_search: async (args) => {
|
||||
const tool = svc.toolMap.get('web_search');
|
||||
if (!tool) return '❌ Web search unavailable.';
|
||||
@@ -426,16 +518,48 @@ export async function initBot(config, api, tools, skills, agents) {
|
||||
return `🔍 *${args.query}*\n\nNo results.`;
|
||||
} catch (e) { return `❌ Search error: ${e.message}`; }
|
||||
},
|
||||
web_fetch: async (args) => {
|
||||
const tool = svc.toolMap.get('web_fetch');
|
||||
if (!tool) return '❌ Web fetch tool unavailable.';
|
||||
try {
|
||||
return await tool.execute(args);
|
||||
} catch (e) { return `❌ Fetch error: ${e.message}`; }
|
||||
},
|
||||
git: async (args) => {
|
||||
const tool = svc.toolMap.get('git');
|
||||
if (!tool) return '❌ Git tool unavailable.';
|
||||
try {
|
||||
const method = tool[args.action];
|
||||
if (!method) return `❌ Unknown: ${args.action}`;
|
||||
if (!method) return `❌ Unknown git action: ${args.action}`;
|
||||
const r = await method.call(tool, ...(args.params || []));
|
||||
return r.success ? `✅ ${r.status || JSON.stringify(r)}` : `❌ ${r.stderr || r.error}`;
|
||||
} catch (e) { return `❌ Git error: ${e.message}`; }
|
||||
},
|
||||
task_create: async (args) => {
|
||||
const tool = svc.toolMap.get('task_create');
|
||||
if (!tool) return '❌ Task tool unavailable.';
|
||||
try { return await tool.execute(args); } catch (e) { return `❌ ${e.message}`; }
|
||||
},
|
||||
task_update: async (args) => {
|
||||
const tool = svc.toolMap.get('task_update');
|
||||
if (!tool) return '❌ Task tool unavailable.';
|
||||
try { return await tool.execute(args); } catch (e) { return `❌ ${e.message}`; }
|
||||
},
|
||||
task_list: async (args) => {
|
||||
const tool = svc.toolMap.get('task_list');
|
||||
if (!tool) return '❌ Task tool unavailable.';
|
||||
try { return await tool.execute(args); } catch (e) { return `❌ ${e.message}`; }
|
||||
},
|
||||
send_message: async (args) => {
|
||||
const tool = svc.toolMap.get('send_message');
|
||||
if (!tool) return '❌ Send message tool unavailable.';
|
||||
try { return await tool.execute(args); } catch (e) { return `❌ ${e.message}`; }
|
||||
},
|
||||
schedule_cron: async (args) => {
|
||||
const tool = svc.toolMap.get('schedule_cron');
|
||||
if (!tool) return '❌ Cron tool unavailable.';
|
||||
try { return await tool.execute(args); } catch (e) { return `❌ ${e.message}`; }
|
||||
},
|
||||
delegate_agent: async (args) => {
|
||||
const agent = svc.agents.find(a => a.id === args.agent_id);
|
||||
if (!agent) return `❌ Agent not found: ${args.agent_id}`;
|
||||
|
||||
Reference in New Issue
Block a user