@measey/mycoder-agent
Version:
Agent module for mycoder - an AI-powered software development assistant
197 lines • 8.66 kB
JavaScript
import chalk from 'chalk';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { getDefaultSystemPrompt, } from '../../core/toolAgent/config.js';
import { toolAgent } from '../../core/toolAgent/toolAgentCore.js';
import { LogLevel, Logger } from '../../utils/logger.js';
import { getTools } from '../getTools.js';
import { AgentStatus } from './AgentTracker.js';
// Generate a random color for an agent
// Avoid colors that are too light or too similar to error/warning colors
const getRandomAgentColor = () => {
// List of bright chalk colors that are visually distinct
const colors = [
chalk.cyan,
chalk.green,
chalk.blue,
chalk.magenta,
chalk.blueBright,
chalk.greenBright,
chalk.cyanBright,
chalk.magentaBright,
];
return colors[Math.floor(Math.random() * colors.length)];
};
const parameterSchema = z.object({
description: z
.string()
.describe("A brief description of the sub-agent's purpose (max 80 chars)"),
goal: z
.string()
.describe('The main objective that the sub-agent needs to achieve'),
projectContext: z
.string()
.describe('Context about the problem or environment'),
workingDirectory: z
.string()
.optional()
.describe('The directory where the sub-agent should operate'),
relevantFilesDirectories: z
.string()
.optional()
.describe('A list of files, which may include ** or * wildcard characters'),
userPrompt: z
.boolean()
.optional()
.describe('Whether to allow the sub-agent to use the userPrompt tool (default: false)'),
});
const returnSchema = z.object({
agentId: z.string().describe('The ID of the started agent process'),
status: z.string().describe('The initial status of the agent'),
});
// Sub-agent specific configuration
const agentConfig = {
maxIterations: 200,
getSystemPrompt: (context) => {
return [
getDefaultSystemPrompt(context),
'You are a focused AI sub-agent handling a specific task.',
'You have access to the same tools as the main agent but should focus only on your assigned task.',
'When complete, call the agentDone tool with your results.',
'Follow any specific conventions or requirements provided in the task context.',
'Ask the main agent for clarification if critical information is missing.',
].join('\n');
},
};
export const agentStartTool = {
name: 'agentStart',
description: 'Starts a sub-agent and returns an instance ID immediately for later interaction',
logPrefix: '🤖',
parameters: parameterSchema,
parametersJsonSchema: zodToJsonSchema(parameterSchema),
returns: returnSchema,
returnsJsonSchema: zodToJsonSchema(returnSchema),
execute: async (params, context) => {
const { logger, agentTracker } = context;
// Validate parameters
const { description, goal, projectContext, workingDirectory, relevantFilesDirectories, userPrompt = false, } = parameterSchema.parse(params);
// Construct a well-structured prompt
const prompt = [
`Description: ${description}`,
`Goal: ${goal}`,
`Project Context: ${projectContext}`,
workingDirectory ? `Working Directory: ${workingDirectory}` : '',
relevantFilesDirectories
? `Relevant Files:\n ${relevantFilesDirectories}`
: '',
]
.filter(Boolean)
.join('\n');
const tools = getTools({ userPrompt });
// Add a logger listener to capture log, warn, and error level messages
const capturedLogs = [];
const logCaptureListener = (logger, logLevel, lines) => {
// Only capture log, warn, and error levels (not debug or info)
if (logLevel === LogLevel.log ||
logLevel === LogLevel.warn ||
logLevel === LogLevel.error) {
// Only capture logs from the agent and its immediate tools (not deeper than that)
// We can identify this by the nesting level of the logger
if (logger.nesting <= 1) {
const logPrefix = logLevel === LogLevel.warn
? '[WARN] '
: logLevel === LogLevel.error
? '[ERROR] '
: '';
// Add each line to the capturedLogs array with logger name for context
lines.forEach((line) => {
const loggerPrefix = logger.name !== 'agent' ? `[${logger.name}] ` : '';
capturedLogs.push(`${logPrefix}${loggerPrefix}${line}`);
});
}
}
};
// Add the listener to the context logger
context.logger.listeners.push(logCaptureListener);
// Create a new logger specifically for the sub-agent if needed
// This is wrapped in a try-catch to maintain backward compatibility with tests
let subAgentLogger = context.logger;
try {
// Generate a random color for this agent
const agentColor = getRandomAgentColor();
subAgentLogger = new Logger({
name: 'agent',
parent: context.logger,
color: agentColor, // Assign the random color to the agent
});
// Add the listener to the sub-agent logger as well
subAgentLogger.listeners.push(logCaptureListener);
}
catch {
// If Logger instantiation fails (e.g., in tests), fall back to using the context logger
context.logger.debug('Failed to create sub-agent logger, using context logger instead');
}
// Register the agent with all the information we have
const agentId = agentTracker.registerAgent({
goal,
prompt,
output: '',
capturedLogs,
completed: false,
context: { ...context },
workingDirectory: workingDirectory ?? context.workingDirectory,
tools,
aborted: false,
parentMessages: [],
});
logger.debug(`Registered agent with ID: ${agentId}`);
// Start the agent in a separate promise that we don't await
// eslint-disable-next-line promise/catch-or-return
Promise.resolve().then(async () => {
try {
const result = await toolAgent(prompt, tools, agentConfig, {
...context,
logger: subAgentLogger, // Use the sub-agent specific logger if available
workingDirectory: workingDirectory ?? context.workingDirectory,
currentAgentId: agentId, // Pass the agent's ID to the context
});
// Update agent with the result
const agent = agentTracker.getAgent(agentId);
if (agent && !agent.aborted) {
agent.completed = true;
agent.result_detailed = result;
agent.output = result.result;
// Update agent tracker with completed status
agentTracker.updateAgentStatus(agentId, AgentStatus.COMPLETED, {
result: result.result.substring(0, 100) +
(result.result.length > 100 ? '...' : ''),
});
}
}
catch (error) {
// Update agent with the error
const agent = agentTracker.getAgent(agentId);
if (agent && !agent.aborted) {
agent.completed = true;
agent.error = error instanceof Error ? error.message : String(error);
// Update agent tracker with error status
agentTracker.updateAgentStatus(agentId, AgentStatus.ERROR, {
error: error instanceof Error ? error.message : String(error),
});
}
}
return true;
});
return {
agentId,
status: 'Agent started successfully',
};
},
logParameters: (input, { logger }) => {
logger.log(`Starting sub-agent for task "${input.description}"`);
},
logReturns: (output, { logger }) => {
logger.log(`Sub-agent started with instance ID: ${output.agentId}`);
},
};
//# sourceMappingURL=agentStart.js.map