Skip to content

Commit 7535f0f

Browse files
authored
Merge pull request #331 from drivecore/feature/colored-agent-logs
Add colored console output for agent logs
2 parents 26f1675 + 77ae98a commit 7535f0f

File tree

16 files changed

+875
-551
lines changed

16 files changed

+875
-551
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ packages/docs/.env.development.local
1515
packages/docs/.env.test.local
1616
packages/docs/.env.production.local
1717
mcp.server.setup.json
18+
coverage

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ MyCoder supports sending corrections to the main agent while it's running. This
129129
### Usage
130130

131131
1. Start MyCoder with the `--interactive` flag:
132+
132133
```bash
133134
mycoder --interactive "Implement a React component"
134135
```

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
"build": "pnpm -r build",
1313
"start": "pnpm -r start",
1414
"test": "pnpm -r test",
15+
"test:coverage": "pnpm -r test:coverage",
1516
"typecheck": "pnpm -r typecheck",
1617
"lint": "eslint . --fix",
1718
"format": "prettier . --write",
18-
"clean": "pnpm -r clean",
19-
"clean:all": "pnpm -r clean:all && rimraf node_modules",
19+
"clean": "rimraf **/dist",
20+
"clean:all": "rimraf **/dist node_modules **/node_modules",
2021
"cloc": "pnpm exec cloc * --exclude-dir=node_modules,dist,.vinxi,.output",
2122
"gcloud-setup": "gcloud auth application-default login && gcloud config set account \"ben@drivecore.ai\" && gcloud config set project drivecore-primary && gcloud config set run/region us-central1",
2223
"cli": "cd packages/cli && node --no-deprecation bin/cli.js",
@@ -71,6 +72,8 @@
7172
"@prisma/client",
7273
"@prisma/engines",
7374
"bcrypt",
75+
"core-js",
76+
"core-js-pure",
7477
"esbuild",
7578
"msw",
7679
"prisma"

packages/agent/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@
2727
"test": "vitest run",
2828
"test:coverage": "vitest run --coverage",
2929
"typecheck": "tsc --noEmit",
30-
"clean": "rimraf dist",
31-
"clean:all": "rimraf node_modules dist",
3230
"semantic-release": "pnpm exec semantic-release -e semantic-release-monorepo"
3331
},
3432
"keywords": [
@@ -62,6 +60,7 @@
6260
"devDependencies": {
6361
"@types/node": "^18",
6462
"@types/uuid": "^10",
63+
"@vitest/coverage-v8": "^3",
6564
"rimraf": "^5",
6665
"type-fest": "^4",
6766
"typescript": "^5",

packages/agent/src/core/toolAgent/toolAgentCore.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,20 @@ export const toolAgent = async (
8888
}
8989
}
9090
}
91-
91+
9292
// Check for messages from user (for main agent only)
9393
// Import this at the top of the file
9494
try {
9595
// Dynamic import to avoid circular dependencies
96-
const { userMessages } = await import('../../tools/interaction/userMessage.js');
97-
96+
const { userMessages } = await import(
97+
'../../tools/interaction/userMessage.js'
98+
);
99+
98100
if (userMessages && userMessages.length > 0) {
99101
// Get all user messages and clear the queue
100102
const pendingUserMessages = [...userMessages];
101103
userMessages.length = 0;
102-
104+
103105
// Add each message to the conversation
104106
for (const message of pendingUserMessages) {
105107
logger.info(`Message from user: ${message}`);

packages/agent/src/tools/agent/__tests__/logCapture.test.ts

+3-36
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
22

3-
import { Logger, LogLevel, LoggerListener } from '../../../utils/logger.js';
3+
import { Logger } from '../../../utils/logger.js';
44
import { agentMessageTool } from '../agentMessage.js';
55
import { agentStartTool } from '../agentStart.js';
6-
import { AgentTracker, AgentState } from '../AgentTracker.js';
6+
import { AgentTracker } from '../AgentTracker.js';
77

88
// Mock the toolAgent function
99
vi.mock('../../../core/toolAgent/toolAgentCore.js', () => ({
@@ -12,33 +12,6 @@ vi.mock('../../../core/toolAgent/toolAgentCore.js', () => ({
1212
.mockResolvedValue({ result: 'Test result', interactions: 1 }),
1313
}));
1414

15-
// Create a real implementation of the log capture function
16-
const createLogCaptureListener = (agentState: AgentState): LoggerListener => {
17-
return (logger, logLevel, lines) => {
18-
// Only capture log, warn, and error levels (not debug or info)
19-
if (
20-
logLevel === LogLevel.log ||
21-
logLevel === LogLevel.warn ||
22-
logLevel === LogLevel.error
23-
) {
24-
// Only capture logs from the agent and its immediate tools (not deeper than that)
25-
if (logger.nesting <= 1) {
26-
const logPrefix =
27-
logLevel === LogLevel.warn
28-
? '[WARN] '
29-
: logLevel === LogLevel.error
30-
? '[ERROR] '
31-
: '';
32-
33-
// Add each line to the capturedLogs array
34-
lines.forEach((line) => {
35-
agentState.capturedLogs.push(`${logPrefix}${line}`);
36-
});
37-
}
38-
}
39-
};
40-
};
41-
4215
describe('Log Capture in AgentTracker', () => {
4316
let agentTracker: AgentTracker;
4417
let logger: Logger;
@@ -78,20 +51,14 @@ describe('Log Capture in AgentTracker', () => {
7851

7952
if (!agentState) return; // TypeScript guard
8053

81-
// Create a tool logger that is a child of the agent logger
82-
const toolLogger = new Logger({
83-
name: 'tool-logger',
84-
parent: context.logger,
85-
});
86-
8754
// For testing purposes, manually add logs to the agent state
8855
// In a real scenario, these would be added by the log listener
8956
agentState.capturedLogs = [
9057
'This log message should be captured',
9158
'[WARN] This warning message should be captured',
9259
'[ERROR] This error message should be captured',
9360
'This tool log message should be captured',
94-
'[WARN] This tool warning message should be captured'
61+
'[WARN] This tool warning message should be captured',
9562
];
9663

9764
// Check that the right messages were captured

packages/agent/src/tools/agent/agentMessage.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,11 @@ export const agentMessageTool: Tool<Parameters, ReturnType> = {
118118
if (output !== 'No output yet' || agentState.capturedLogs.length > 0) {
119119
const logContent = agentState.capturedLogs.join('\n');
120120
output = `${output}\n\n--- Agent Log Messages ---\n${logContent}`;
121-
121+
122122
// Log that we're returning captured logs
123-
logger.debug(`Returning ${agentState.capturedLogs.length} captured log messages for agent ${instanceId}`);
123+
logger.debug(
124+
`Returning ${agentState.capturedLogs.length} captured log messages for agent ${instanceId}`,
125+
);
124126
}
125127
// Clear the captured logs after retrieving them
126128
agentState.capturedLogs = [];

packages/agent/src/tools/agent/agentStart.ts

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import chalk from 'chalk';
12
import { z } from 'zod';
23
import { zodToJsonSchema } from 'zod-to-json-schema';
34

@@ -15,6 +16,23 @@ import { AgentStatus, AgentState } from './AgentTracker.js';
1516
// For backward compatibility
1617
export const agentStates = new Map<string, AgentState>();
1718

19+
// Generate a random color for an agent
20+
// Avoid colors that are too light or too similar to error/warning colors
21+
const getRandomAgentColor = () => {
22+
// List of bright chalk colors that are visually distinct
23+
const colors = [
24+
chalk.cyan,
25+
chalk.green,
26+
chalk.blue,
27+
chalk.magenta,
28+
chalk.blueBright,
29+
chalk.greenBright,
30+
chalk.cyanBright,
31+
chalk.magentaBright,
32+
];
33+
return colors[Math.floor(Math.random() * colors.length)];
34+
};
35+
1836
const parameterSchema = z.object({
1937
description: z
2038
.string()
@@ -141,7 +159,8 @@ export const agentStartTool: Tool<Parameters, ReturnType> = {
141159

142160
// Add each line to the capturedLogs array with logger name for context
143161
lines.forEach((line) => {
144-
const loggerPrefix = logger.name !== 'agent' ? `[${logger.name}] ` : '';
162+
const loggerPrefix =
163+
logger.name !== 'agent' ? `[${logger.name}] ` : '';
145164
agentState.capturedLogs.push(`${logPrefix}${loggerPrefix}${line}`);
146165
});
147166
}
@@ -150,20 +169,26 @@ export const agentStartTool: Tool<Parameters, ReturnType> = {
150169

151170
// Add the listener to the context logger
152171
context.logger.listeners.push(logCaptureListener);
153-
172+
154173
// Create a new logger specifically for the sub-agent if needed
155174
// This is wrapped in a try-catch to maintain backward compatibility with tests
156175
let subAgentLogger = context.logger;
157176
try {
177+
// Generate a random color for this agent
178+
const agentColor = getRandomAgentColor();
179+
158180
subAgentLogger = new Logger({
159181
name: 'agent',
160182
parent: context.logger,
183+
color: agentColor, // Assign the random color to the agent
161184
});
162185
// Add the listener to the sub-agent logger as well
163186
subAgentLogger.listeners.push(logCaptureListener);
164187
} catch {
165188
// If Logger instantiation fails (e.g., in tests), fall back to using the context logger
166-
context.logger.debug('Failed to create sub-agent logger, using context logger instead');
189+
context.logger.debug(
190+
'Failed to create sub-agent logger, using context logger instead',
191+
);
167192
}
168193

169194
// Register agent state with the tracker

0 commit comments

Comments
 (0)