Skip to content

Commit f037f14

Browse files
committed
Merge remote-tracking branch 'origin/feature/agent-tracker-decoupling'
2 parents f13bbb9 + eda4640 commit f037f14

File tree

6 files changed

+345
-90
lines changed

6 files changed

+345
-90
lines changed

packages/agent/src/core/backgroundTools.cleanup.test.ts

+49-56
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,27 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
33
// Import mocked modules
44
import { BrowserManager } from '../tools/browser/BrowserManager.js';
55
import { agentStates } from '../tools/interaction/agentStart.js';
6+
import { agentTracker } from '../tools/interaction/agentTracker.js';
67
import { shellTracker } from '../tools/system/ShellTracker.js';
78

89
import { BackgroundTools, BackgroundToolStatus } from './backgroundTools';
9-
import { Tool } from './types';
10+
11+
// Import the ChildProcess type for mocking
12+
import type { ChildProcess } from 'child_process';
1013

1114
// Define types for our mocks that match the actual types
1215
type MockProcessState = {
13-
process: { kill: ReturnType<typeof vi.fn> };
14-
state: { completed: boolean };
15-
command?: string;
16-
stdout?: string[];
17-
stderr?: string[];
18-
showStdIn?: boolean;
19-
showStdout?: boolean;
20-
};
21-
22-
type MockAgentState = {
23-
aborted: boolean;
24-
completed: boolean;
25-
context: {
26-
backgroundTools: {
27-
cleanup: ReturnType<typeof vi.fn>;
28-
};
16+
process: ChildProcess & { kill: ReturnType<typeof vi.fn> };
17+
state: {
18+
completed: boolean;
19+
signaled: boolean;
20+
exitCode: number | null;
2921
};
30-
goal?: string;
31-
prompt?: string;
32-
output?: string;
33-
workingDirectory?: string;
34-
tools?: Tool[];
22+
command: string;
23+
stdout: string[];
24+
stderr: string[];
25+
showStdIn: boolean;
26+
showStdout: boolean;
3527
};
3628

3729
// Mock dependencies
@@ -52,9 +44,28 @@ vi.mock('../tools/system/ShellTracker.js', () => {
5244
};
5345
});
5446

55-
vi.mock('../tools/interaction/agentStart.js', () => {
47+
vi.mock('../tools/interaction/agentTracker.js', () => {
5648
return {
57-
agentStates: new Map<string, MockAgentState>(),
49+
agentTracker: {
50+
terminateAgent: vi.fn().mockResolvedValue(undefined),
51+
getAgentState: vi.fn().mockImplementation((id: string) => {
52+
return {
53+
id,
54+
aborted: false,
55+
completed: false,
56+
context: {
57+
backgroundTools: {
58+
cleanup: vi.fn().mockResolvedValue(undefined),
59+
},
60+
},
61+
goal: 'test goal',
62+
prompt: 'test prompt',
63+
output: '',
64+
workingDirectory: '/test',
65+
tools: [],
66+
};
67+
}),
68+
},
5869
};
5970
});
6071

@@ -78,11 +89,19 @@ describe('BackgroundTools cleanup', () => {
7889
// Setup mock process states
7990
const mockProcess = {
8091
kill: vi.fn(),
81-
};
92+
stdin: null,
93+
stdout: null,
94+
stderr: null,
95+
stdio: null,
96+
} as unknown as ChildProcess & { kill: ReturnType<typeof vi.fn> };
8297

8398
const mockProcessState: MockProcessState = {
8499
process: mockProcess,
85-
state: { completed: false },
100+
state: {
101+
completed: false,
102+
signaled: false,
103+
exitCode: null,
104+
},
86105
command: 'test command',
87106
stdout: [],
88107
stderr: [],
@@ -93,24 +112,8 @@ describe('BackgroundTools cleanup', () => {
93112
shellTracker.processStates.clear();
94113
shellTracker.processStates.set('shell-1', mockProcessState as any);
95114

96-
// Setup mock agent states
97-
const mockAgentState: MockAgentState = {
98-
aborted: false,
99-
completed: false,
100-
context: {
101-
backgroundTools: {
102-
cleanup: vi.fn().mockResolvedValue(undefined),
103-
},
104-
},
105-
goal: 'test goal',
106-
prompt: 'test prompt',
107-
output: '',
108-
workingDirectory: '/test',
109-
tools: [],
110-
};
111-
112-
agentStates.clear();
113-
agentStates.set('agent-1', mockAgentState as any);
115+
// Reset the agentTracker mock
116+
vi.mocked(agentTracker.terminateAgent).mockClear();
114117
});
115118

116119
afterEach(() => {
@@ -156,21 +159,11 @@ describe('BackgroundTools cleanup', () => {
156159
// Register an agent tool
157160
const agentId = backgroundTools.registerAgent('Test goal');
158161

159-
// Get mock agent state
160-
const mockAgentState = agentStates.get('agent-1');
161-
162-
// Set the agent ID to match
163-
agentStates.set(agentId, agentStates.get('agent-1') as any);
164-
165162
// Run cleanup
166163
await backgroundTools.cleanup();
167164

168-
// Check that agent was marked as aborted
169-
expect(mockAgentState?.aborted).toBe(true);
170-
expect(mockAgentState?.completed).toBe(true);
171-
172-
// Check that cleanup was called on the agent's background tools
173-
expect(mockAgentState?.context.backgroundTools.cleanup).toHaveBeenCalled();
165+
// Check that terminateAgent was called with the agent ID
166+
expect(agentTracker.terminateAgent).toHaveBeenCalledWith(agentId);
174167

175168
// Check that tool status was updated
176169
const tool = backgroundTools.getToolById(agentId);

packages/agent/src/core/backgroundTools.ts

+3-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { v4 as uuidv4 } from 'uuid';
22

33
// These imports will be used by the cleanup method
44
import { BrowserManager } from '../tools/browser/BrowserManager.js';
5-
import { agentStates } from '../tools/interaction/agentStart.js';
5+
import { agentTracker } from '../tools/interaction/agentTracker.js';
66
import { shellTracker } from '../tools/system/ShellTracker.js';
77

88
// Types of background processes we can track
@@ -195,15 +195,8 @@ export class BackgroundTools {
195195
*/
196196
private async cleanupSubAgent(tool: AgentBackgroundTool): Promise<void> {
197197
try {
198-
const agentState = agentStates.get(tool.id);
199-
if (agentState && !agentState.aborted) {
200-
// Set the agent as aborted and completed
201-
agentState.aborted = true;
202-
agentState.completed = true;
203-
204-
// Clean up resources owned by this sub-agent
205-
await agentState.context.backgroundTools.cleanup();
206-
}
198+
// Delegate to the agent tracker
199+
await agentTracker.terminateAgent(tool.id);
207200
this.updateToolStatus(tool.id, BackgroundToolStatus.TERMINATED);
208201
} catch (error) {
209202
this.updateToolStatus(tool.id, BackgroundToolStatus.ERROR, {

packages/agent/src/tools/getTools.ts

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { userPromptTool } from './interaction/userPrompt.js';
99
import { fetchTool } from './io/fetch.js';
1010
import { textEditorTool } from './io/textEditor.js';
1111
import { createMcpTool } from './mcp.js';
12+
import { listAgentsTool } from './system/listAgents.js';
1213
import { listBackgroundToolsTool } from './system/listBackgroundTools.js';
1314
import { listShellsTool } from './system/listShells.js';
1415
import { sequenceCompleteTool } from './system/sequenceComplete.js';
@@ -43,6 +44,7 @@ export function getTools(options?: GetToolsOptions): Tool[] {
4344
sleepTool as unknown as Tool,
4445
listBackgroundToolsTool as unknown as Tool,
4546
listShellsTool as unknown as Tool,
47+
listAgentsTool as unknown as Tool,
4648
];
4749

4850
// Only include userPrompt tool if enabled

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

+28-24
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { v4 as uuidv4 } from 'uuid';
21
import { z } from 'zod';
32
import { zodToJsonSchema } from 'zod-to-json-schema';
43

@@ -8,26 +7,13 @@ import {
87
AgentConfig,
98
} from '../../core/toolAgent/config.js';
109
import { toolAgent } from '../../core/toolAgent/toolAgentCore.js';
11-
import { ToolAgentResult } from '../../core/toolAgent/types.js';
1210
import { Tool, ToolContext } from '../../core/types.js';
1311
import { getTools } from '../getTools.js';
1412

15-
// Define AgentState type
16-
type AgentState = {
17-
goal: string;
18-
prompt: string;
19-
output: string;
20-
completed: boolean;
21-
error?: string;
22-
result?: ToolAgentResult;
23-
context: ToolContext;
24-
workingDirectory: string;
25-
tools: Tool[];
26-
aborted: boolean;
27-
};
13+
import { AgentStatus, agentTracker, AgentState } from './agentTracker.js';
2814

29-
// Global map to store agent state
30-
export const agentStates: Map<string, AgentState> = new Map();
15+
// For backward compatibility
16+
export const agentStates = new Map<string, AgentState>();
3117

3218
const parameterSchema = z.object({
3319
description: z
@@ -100,11 +86,12 @@ export const agentStartTool: Tool<Parameters, ReturnType> = {
10086
userPrompt = false,
10187
} = parameterSchema.parse(params);
10288

103-
// Create an instance ID
104-
const instanceId = uuidv4();
89+
// Register this agent with the agent tracker
90+
const instanceId = agentTracker.registerAgent(goal);
10591

106-
// Register this agent with the background tool registry
92+
// For backward compatibility, also register with background tools
10793
backgroundTools.registerAgent(goal);
94+
10895
logger.verbose(`Registered agent with ID: ${instanceId}`);
10996

11097
// Construct a well-structured prompt
@@ -124,6 +111,7 @@ export const agentStartTool: Tool<Parameters, ReturnType> = {
124111

125112
// Store the agent state
126113
const agentState: AgentState = {
114+
id: instanceId,
127115
goal,
128116
prompt,
129117
output: '',
@@ -134,6 +122,10 @@ export const agentStartTool: Tool<Parameters, ReturnType> = {
134122
aborted: false,
135123
};
136124

125+
// Register agent state with the tracker
126+
agentTracker.registerAgentState(instanceId, agentState);
127+
128+
// For backward compatibility
137129
agentStates.set(instanceId, agentState);
138130

139131
// Start the agent in a separate promise that we don't await
@@ -146,13 +138,20 @@ export const agentStartTool: Tool<Parameters, ReturnType> = {
146138
});
147139

148140
// Update agent state with the result
149-
const state = agentStates.get(instanceId);
141+
const state = agentTracker.getAgentState(instanceId);
150142
if (state && !state.aborted) {
151143
state.completed = true;
152144
state.result = result;
153145
state.output = result.result;
154146

155-
// Update background tool registry with completed status
147+
// Update agent tracker with completed status
148+
agentTracker.updateAgentStatus(instanceId, AgentStatus.COMPLETED, {
149+
result:
150+
result.result.substring(0, 100) +
151+
(result.result.length > 100 ? '...' : ''),
152+
});
153+
154+
// For backward compatibility
156155
backgroundTools.updateToolStatus(
157156
instanceId,
158157
BackgroundToolStatus.COMPLETED,
@@ -168,12 +167,17 @@ export const agentStartTool: Tool<Parameters, ReturnType> = {
168167
}
169168
} catch (error) {
170169
// Update agent state with the error
171-
const state = agentStates.get(instanceId);
170+
const state = agentTracker.getAgentState(instanceId);
172171
if (state && !state.aborted) {
173172
state.completed = true;
174173
state.error = error instanceof Error ? error.message : String(error);
175174

176-
// Update background tool registry with error status
175+
// Update agent tracker with error status
176+
agentTracker.updateAgentStatus(instanceId, AgentStatus.ERROR, {
177+
error: error instanceof Error ? error.message : String(error),
178+
});
179+
180+
// For backward compatibility
177181
backgroundTools.updateToolStatus(
178182
instanceId,
179183
BackgroundToolStatus.ERROR,

0 commit comments

Comments
 (0)