src / tools / agentTools.ts
src / tools / agentTools.ts
/**
* Agent Management Tools
* Provides better control for creating, maintaining, and managing sub-agents with universal tool access.
*/
import { z } from 'zod';
import fs from 'fs/promises';
import path from 'path';
// Agent registry (in-memory with file persistence)
declare global {
var __agent_registry__: Map<string, {
id: string;
name: string;
role: string;
status: 'active' | 'paused' | 'completed' | 'failed';
tools_enabled: string[];
created_at: number;
last_active: number;
config: Record<string, any>;
}>;
}
if (!global.__agent_registry__) {
global.__agent_registry__ = new Map();
}
// Agent task queue for managing parallel execution
interface AgentTask {
id: string;
agent_id: string;
task: string;
priority: number;
status: 'pending' | 'running' | 'completed' | 'failed';
result?: any;
error?: string;
created_at: number;
}
let agentTaskQueue: AgentTask[] = [];
// Zod schemas for validation
const createAgentSchema = z.object({
name: z.string().min(1),
role: z.enum(['coder', 'reviewer', 'researcher', 'general', 'custom']),
tools_enabled: z.array(z.string()).optional(),
config: z.record(z.any()).optional(),
});
const updateAgentSchema = z.object({
agent_id: z.string(),
status: z.enum(['active', 'paused', 'completed', 'failed']).optional(),
tools_enabled: z.array(z.string()).optional(),
});
const deleteAgentSchema = z.object({
agent_id: z.string(),
});
const assignTaskSchema = z.object({
agent_id: z.string(),
task: z.string().min(1),
priority: z.number().int().min(0).max(100).optional().default(50),
});
/**
* Create a new sub-agent with universal tool access.
*/
export async function agent_create(name: string, role: 'coder' | 'reviewer' | 'researcher' | 'general' | 'custom', tools_enabled?: string[], config?: Record<string, any>): Promise<{ agent_id: string; status: string }> {
const validated = createAgentSchema.parse({ name, role, tools_enabled, config });
const agent_id = `agent_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Grant universal tool access by default (as requested)
const allTools = [
'read_file', 'save_file', 'list_directory', 'change_directory', 'make_directory',
'delete_path', 'move_file', 'copy_file', 'find_files', 'fuzzy_find_local_files',
'replace_text_in_file', 'multi_replace_text', 'insert_at_line', 'append_file',
'read_file_range', 'search_in_file', 'delete_lines_in_file', 'get_file_metadata',
'run_javascript', 'run_python', 'execute_command', 'run_in_terminal',
'web_search', 'fetch_web_content', 'wikipedia_search', 'rag_local_files',
'browser_session_open', 'browser_session_control', 'browser_session_close',
'git_status', 'git_diff', 'git_commit', 'git_log', 'git_add', 'git_checkout',
'gh_auth', 'gh_create_issue', 'gh_list_issues', 'gh_view_comments',
'gh_create_pr', 'gh_list_prs', 'gh_view_pr_diff', 'gh_push',
'neo4j_query', 'neo4j_create_node', 'neo4j_delete_node', 'neo4j_update_node',
'neo4j_create_relationship', 'neo4j_delete_relationship', 'neo4j_path_find',
'neo4j_graph_analysis',
'postgres_query', 'postgres_backup', 'postgres_restore', 'postgres_schema_diff',
'postgres_export_csv',
'email_send', 'email_attach_file', 'email_template_render', 'email_verify_address',
'rss_fetch', 'atom_parse', 'feed_monitor_check',
'image_resize', 'image_convert_format', 'image_extract_metadata',
'image_create_thumbnail', 'pdf_merge_pages',
'video_transcode', 'audio_extract', 'media_info_get', 'gif_animate_from_video',
'zip_create', 'zip_extract', 'tar_create', 'tar_extract', 'archive_list_contents',
'csv_to_json_stream', 'json_to_csv_convert', 'csv_validate_schema', 'csv_merge_files',
'yaml_parse_file', 'yaml_generate_file', 'config_diff_yaml', 'yaml_to_json',
'cron_schedule', 'cron_list', 'cron_delete', 'task_queue_add', 'workflow_execute',
'timer_start', 'timer_stop', 'timer_status', 'time_log_create', 'time_log_entry',
'time_log_report', 'calendar_add_event', 'reminder_set',
'json_merge', 'regex_test', 'url_build', 'hash_generate', 'uuid_generate', 'uuid_validate'
];
const agent = {
id: agent_id,
name: validated.name,
role: validated.role,
status: 'active' as const,
tools_enabled: validated.tools_enabled || allTools, // Universal access by default
created_at: Date.now(),
last_active: Date.now(),
config: validated.config || {}
};
global.__agent_registry__.set(agent_id, agent);
// Persist to file
await persistAgentRegistry();
return { agent_id, status: 'active' };
}
/**
* Update an existing sub-agent.
*/
export async function agent_update(agent_id: string, status?: 'active' | 'paused' | 'completed' | 'failed', tools_enabled?: string[]): Promise<{ success: boolean; message: string }> {
const validated = updateAgentSchema.parse({ agent_id, status, tools_enabled });
const agent = global.__agent_registry__.get(agent_id);
if (!agent) {
return { success: false, message: `Agent not found: ${agent_id}` };
}
if (validated.status) {
agent.status = validated.status;
}
if (validated.tools_enabled !== undefined) {
agent.tools_enabled = validated.tools_enabled;
}
agent.last_active = Date.now();
// Persist to file
await persistAgentRegistry();
return { success: true, message: `Agent ${agent_id} updated successfully` };
}
/**
* Delete a sub-agent.
*/
export async function agent_delete(agent_id: string): Promise<{ success: boolean; message: string }> {
const validated = deleteAgentSchema.parse({ agent_id });
if (!global.__agent_registry__.has(agent_id)) {
return { success: false, message: `Agent not found: ${agent_id}` };
}
global.__agent_registry__.delete(agent_id);
// Persist to file
await persistAgentRegistry();
return { success: true, message: `Agent ${agent_id} deleted successfully` };
}
/**
* Assign a task to an agent.
*/
export async function agent_assign_task(agent_id: string, task: string, priority?: number): Promise<{ task_id: string; status: string }> {
const validated = assignTaskSchema.parse({ agent_id, task, priority });
const agent = global.__agent_registry__.get(agent_id);
if (!agent) {
throw new Error(`Agent not found: ${agent_id}`);
}
if (agent.status !== 'active') {
throw new Error(`Agent is not active. Current status: ${agent.status}`);
}
const task_id = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const agentTask: AgentTask = {
id: task_id,
agent_id,
task: validated.task,
priority: validated.priority || 50,
status: 'pending' as const,
created_at: Date.now()
};
// Insert into queue maintaining priority order
let position = 0;
for (const existing of agentTaskQueue) {
if (existing.priority > agentTask.priority) {
break;
}
position++;
}
agentTaskQueue.splice(position, 0, agentTask);
return { task_id, status: 'pending' };
}
/**
* Get status of all agents.
*/
export async function agent_status(): Promise<{ agents: Array<{ id: string; name: string; role: string; status: string; tools_count: number }> }> {
const agents = [];
for (const [id, agent] of global.__agent_registry__.entries()) {
agents.push({
id,
name: agent.name,
role: agent.role,
status: agent.status,
tools_count: agent.tools_enabled.length
});
}
return { agents };
}
/**
* Get task queue status.
*/
export async function agent_task_queue_status(): Promise<{ tasks: Array<{ id: string; agent_id: string; task: string; priority: number; status: string }> }> {
return { tasks: agentTaskQueue.map(t => ({ ...t })) };
}
/**
* Persist agent registry to file.
*/
async function persistAgentRegistry(): Promise<void> {
const storagePath = path.join(process.cwd(), '.agent_registry.json');
try {
const agentsData: Array<{ id: string; name: string; role: string; status: string; tools_enabled: string[]; created_at: number; last_active: number }> = [];
for (const [id, agent] of global.__agent_registry__.entries()) {
agentsData.push({
id,
name: agent.name,
role: agent.role,
status: agent.status,
tools_enabled: agent.tools_enabled,
created_at: agent.created_at,
last_active: agent.last_active
});
}
await fs.writeFile(storagePath, JSON.stringify(agentsData, null, 2));
} catch (error) {
console.error(`Failed to persist agent registry: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Delegate a task from one sub-agent to another (nested delegation)
*/
export async function delegate_to_agent(
parentTaskId: string,
task: string,
priority?: number,
toolsEnabled?: string[]
): Promise<{ success: boolean; childAgentId?: string }> {
const state = require('../stateRegistry').getState();
const parentAgent = state.activeAgents.get(parentTaskId);
if (!parentAgent || parentAgent.status !== 'active') {
return { success: false };
}
const childAgentId = `child_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
state.activeAgents.set(childAgentId, {
name: `delegate_of_${parentAgent.name}`,
role: parentAgent.role,
task: `${task}\n\n[Delegated from agent ${parentTaskId}]`,
status: 'active',
parentTask: parentTaskId
});
return { success: true, childAgentId };
}
/**
* Get delegation chain for an agent
*/
export async function getDelegationChain(agentId: string): Promise<{ success: boolean; chain: Array<{ id: string; task: string }> }> {
const state = require('../stateRegistry').getState();
const chain: Array<{ id: string; task: string }> = [];
let currentId = agentId;
while (currentId) {
const agent = state.activeAgents.get(currentId);
if (!agent) break;
chain.unshift({ id: currentId, task: agent.task });
currentId = agent.parentTask || null;
}
return { success: true, chain };
}
/**
* Run a task with automatic self-correction on failure.
* Actually executes a sub-agent loop with the specified role and task.
*/
export async function agent_run_with_retry(
client: any,
pluginConfig: any,
name: string,
role: string,
task: string,
maxRetries: number = 3,
toolsEnabled?: string[],
currentWorkingDirectory?: string
): Promise<{ success: boolean; attempts: number; finalResult?: string }> {
let lastError = '';
const cwd = currentWorkingDirectory || process.cwd();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
// Import the shared delegation engine
const { runSubAgentLoop } = await import('../toolsProvider');
const result = await runSubAgentLoop(
client,
pluginConfig,
role,
task,
'',
8,
false,
cwd,
true // Enable tools by default for agent_run_with_retry
);
// Check if files were actually modified
if (result.generated_files.length > 0) {
return { success: true, attempts: attempt, finalResult: `Task completed. Files modified: ${result.generated_files.join(', ')}` };
}
// Check if sub-agent produced a meaningful response
if (result.response && result.response.length > 10) {
return { success: true, attempts: attempt, finalResult: result.response };
}
// If no files modified and no response, try again
lastError = 'No files modified and no meaningful response from sub-agent.';
} catch (err: any) {
lastError = err.message;
if (attempt >= maxRetries) break;
}
}
return { success: false, attempts: maxRetries, finalResult: `Failed after ${maxRetries} attempts. Last error: ${lastError}` };
}
/**
* Agent Management Tools
* Provides better control for creating, maintaining, and managing sub-agents with universal tool access.
*/
import { z } from 'zod';
import fs from 'fs/promises';
import path from 'path';
// Agent registry (in-memory with file persistence)
declare global {
var __agent_registry__: Map<string, {
id: string;
name: string;
role: string;
status: 'active' | 'paused' | 'completed' | 'failed';
tools_enabled: string[];
created_at: number;
last_active: number;
config: Record<string, any>;
}>;
}
if (!global.__agent_registry__) {
global.__agent_registry__ = new Map();
}
// Agent task queue for managing parallel execution
interface AgentTask {
id: string;
agent_id: string;
task: string;
priority: number;
status: 'pending' | 'running' | 'completed' | 'failed';
result?: any;
error?: string;
created_at: number;
}
let agentTaskQueue: AgentTask[] = [];
// Zod schemas for validation
const createAgentSchema = z.object({
name: z.string().min(1),
role: z.enum(['coder', 'reviewer', 'researcher', 'general', 'custom']),
tools_enabled: z.array(z.string()).optional(),
config: z.record(z.any()).optional(),
});
const updateAgentSchema = z.object({
agent_id: z.string(),
status: z.enum(['active', 'paused', 'completed', 'failed']).optional(),
tools_enabled: z.array(z.string()).optional(),
});
const deleteAgentSchema = z.object({
agent_id: z.string(),
});
const assignTaskSchema = z.object({
agent_id: z.string(),
task: z.string().min(1),
priority: z.number().int().min(0).max(100).optional().default(50),
});
/**
* Create a new sub-agent with universal tool access.
*/
export async function agent_create(name: string, role: 'coder' | 'reviewer' | 'researcher' | 'general' | 'custom', tools_enabled?: string[], config?: Record<string, any>): Promise<{ agent_id: string; status: string }> {
const validated = createAgentSchema.parse({ name, role, tools_enabled, config });
const agent_id = `agent_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Grant universal tool access by default (as requested)
const allTools = [
'read_file', 'save_file', 'list_directory', 'change_directory', 'make_directory',
'delete_path', 'move_file', 'copy_file', 'find_files', 'fuzzy_find_local_files',
'replace_text_in_file', 'multi_replace_text', 'insert_at_line', 'append_file',
'read_file_range', 'search_in_file', 'delete_lines_in_file', 'get_file_metadata',
'run_javascript', 'run_python', 'execute_command', 'run_in_terminal',
'web_search', 'fetch_web_content', 'wikipedia_search', 'rag_local_files',
'browser_session_open', 'browser_session_control', 'browser_session_close',
'git_status', 'git_diff', 'git_commit', 'git_log', 'git_add', 'git_checkout',
'gh_auth', 'gh_create_issue', 'gh_list_issues', 'gh_view_comments',
'gh_create_pr', 'gh_list_prs', 'gh_view_pr_diff', 'gh_push',
'neo4j_query', 'neo4j_create_node', 'neo4j_delete_node', 'neo4j_update_node',
'neo4j_create_relationship', 'neo4j_delete_relationship', 'neo4j_path_find',
'neo4j_graph_analysis',
'postgres_query', 'postgres_backup', 'postgres_restore', 'postgres_schema_diff',
'postgres_export_csv',
'email_send', 'email_attach_file', 'email_template_render', 'email_verify_address',
'rss_fetch', 'atom_parse', 'feed_monitor_check',
'image_resize', 'image_convert_format', 'image_extract_metadata',
'image_create_thumbnail', 'pdf_merge_pages',
'video_transcode', 'audio_extract', 'media_info_get', 'gif_animate_from_video',
'zip_create', 'zip_extract', 'tar_create', 'tar_extract', 'archive_list_contents',
'csv_to_json_stream', 'json_to_csv_convert', 'csv_validate_schema', 'csv_merge_files',
'yaml_parse_file', 'yaml_generate_file', 'config_diff_yaml', 'yaml_to_json',
'cron_schedule', 'cron_list', 'cron_delete', 'task_queue_add', 'workflow_execute',
'timer_start', 'timer_stop', 'timer_status', 'time_log_create', 'time_log_entry',
'time_log_report', 'calendar_add_event', 'reminder_set',
'json_merge', 'regex_test', 'url_build', 'hash_generate', 'uuid_generate', 'uuid_validate'
];
const agent = {
id: agent_id,
name: validated.name,
role: validated.role,
status: 'active' as const,
tools_enabled: validated.tools_enabled || allTools, // Universal access by default
created_at: Date.now(),
last_active: Date.now(),
config: validated.config || {}
};
global.__agent_registry__.set(agent_id, agent);
// Persist to file
await persistAgentRegistry();
return { agent_id, status: 'active' };
}
/**
* Update an existing sub-agent.
*/
export async function agent_update(agent_id: string, status?: 'active' | 'paused' | 'completed' | 'failed', tools_enabled?: string[]): Promise<{ success: boolean; message: string }> {
const validated = updateAgentSchema.parse({ agent_id, status, tools_enabled });
const agent = global.__agent_registry__.get(agent_id);
if (!agent) {
return { success: false, message: `Agent not found: ${agent_id}` };
}
if (validated.status) {
agent.status = validated.status;
}
if (validated.tools_enabled !== undefined) {
agent.tools_enabled = validated.tools_enabled;
}
agent.last_active = Date.now();
// Persist to file
await persistAgentRegistry();
return { success: true, message: `Agent ${agent_id} updated successfully` };
}
/**
* Delete a sub-agent.
*/
export async function agent_delete(agent_id: string): Promise<{ success: boolean; message: string }> {
const validated = deleteAgentSchema.parse({ agent_id });
if (!global.__agent_registry__.has(agent_id)) {
return { success: false, message: `Agent not found: ${agent_id}` };
}
global.__agent_registry__.delete(agent_id);
// Persist to file
await persistAgentRegistry();
return { success: true, message: `Agent ${agent_id} deleted successfully` };
}
/**
* Assign a task to an agent.
*/
export async function agent_assign_task(agent_id: string, task: string, priority?: number): Promise<{ task_id: string; status: string }> {
const validated = assignTaskSchema.parse({ agent_id, task, priority });
const agent = global.__agent_registry__.get(agent_id);
if (!agent) {
throw new Error(`Agent not found: ${agent_id}`);
}
if (agent.status !== 'active') {
throw new Error(`Agent is not active. Current status: ${agent.status}`);
}
const task_id = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const agentTask: AgentTask = {
id: task_id,
agent_id,
task: validated.task,
priority: validated.priority || 50,
status: 'pending' as const,
created_at: Date.now()
};
// Insert into queue maintaining priority order
let position = 0;
for (const existing of agentTaskQueue) {
if (existing.priority > agentTask.priority) {
break;
}
position++;
}
agentTaskQueue.splice(position, 0, agentTask);
return { task_id, status: 'pending' };
}
/**
* Get status of all agents.
*/
export async function agent_status(): Promise<{ agents: Array<{ id: string; name: string; role: string; status: string; tools_count: number }> }> {
const agents = [];
for (const [id, agent] of global.__agent_registry__.entries()) {
agents.push({
id,
name: agent.name,
role: agent.role,
status: agent.status,
tools_count: agent.tools_enabled.length
});
}
return { agents };
}
/**
* Get task queue status.
*/
export async function agent_task_queue_status(): Promise<{ tasks: Array<{ id: string; agent_id: string; task: string; priority: number; status: string }> }> {
return { tasks: agentTaskQueue.map(t => ({ ...t })) };
}
/**
* Persist agent registry to file.
*/
async function persistAgentRegistry(): Promise<void> {
const storagePath = path.join(process.cwd(), '.agent_registry.json');
try {
const agentsData: Array<{ id: string; name: string; role: string; status: string; tools_enabled: string[]; created_at: number; last_active: number }> = [];
for (const [id, agent] of global.__agent_registry__.entries()) {
agentsData.push({
id,
name: agent.name,
role: agent.role,
status: agent.status,
tools_enabled: agent.tools_enabled,
created_at: agent.created_at,
last_active: agent.last_active
});
}
await fs.writeFile(storagePath, JSON.stringify(agentsData, null, 2));
} catch (error) {
console.error(`Failed to persist agent registry: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Delegate a task from one sub-agent to another (nested delegation)
*/
export async function delegate_to_agent(
parentTaskId: string,
task: string,
priority?: number,
toolsEnabled?: string[]
): Promise<{ success: boolean; childAgentId?: string }> {
const state = require('../stateRegistry').getState();
const parentAgent = state.activeAgents.get(parentTaskId);
if (!parentAgent || parentAgent.status !== 'active') {
return { success: false };
}
const childAgentId = `child_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
state.activeAgents.set(childAgentId, {
name: `delegate_of_${parentAgent.name}`,
role: parentAgent.role,
task: `${task}\n\n[Delegated from agent ${parentTaskId}]`,
status: 'active',
parentTask: parentTaskId
});
return { success: true, childAgentId };
}
/**
* Get delegation chain for an agent
*/
export async function getDelegationChain(agentId: string): Promise<{ success: boolean; chain: Array<{ id: string; task: string }> }> {
const state = require('../stateRegistry').getState();
const chain: Array<{ id: string; task: string }> = [];
let currentId = agentId;
while (currentId) {
const agent = state.activeAgents.get(currentId);
if (!agent) break;
chain.unshift({ id: currentId, task: agent.task });
currentId = agent.parentTask || null;
}
return { success: true, chain };
}
/**
* Run a task with automatic self-correction on failure.
* Actually executes a sub-agent loop with the specified role and task.
*/
export async function agent_run_with_retry(
client: any,
pluginConfig: any,
name: string,
role: string,
task: string,
maxRetries: number = 3,
toolsEnabled?: string[],
currentWorkingDirectory?: string
): Promise<{ success: boolean; attempts: number; finalResult?: string }> {
let lastError = '';
const cwd = currentWorkingDirectory || process.cwd();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
// Import the shared delegation engine
const { runSubAgentLoop } = await import('../toolsProvider');
const result = await runSubAgentLoop(
client,
pluginConfig,
role,
task,
'',
8,
false,
cwd,
true // Enable tools by default for agent_run_with_retry
);
// Check if files were actually modified
if (result.generated_files.length > 0) {
return { success: true, attempts: attempt, finalResult: `Task completed. Files modified: ${result.generated_files.join(', ')}` };
}
// Check if sub-agent produced a meaningful response
if (result.response && result.response.length > 10) {
return { success: true, attempts: attempt, finalResult: result.response };
}
// If no files modified and no response, try again
lastError = 'No files modified and no meaningful response from sub-agent.';
} catch (err: any) {
lastError = err.message;
if (attempt >= maxRetries) break;
}
}
return { success: false, attempts: maxRetries, finalResult: `Failed after ${maxRetries} attempts. Last error: ${lastError}` };
}