@mastra/agent-builder 0.0.1-alpha.1 → 0.0.1-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/agent/index.d.ts +5885 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/defaults.d.ts +6529 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1810 -36
- package/dist/index.js.map +1 -0
- package/dist/processors/tool-summary.d.ts +29 -0
- package/dist/processors/tool-summary.d.ts.map +1 -0
- package/dist/processors/write-file.d.ts +10 -0
- package/dist/processors/write-file.d.ts.map +1 -0
- package/dist/types.d.ts +1121 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +63 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/workflows/index.d.ts +5 -0
- package/dist/workflows/index.d.ts.map +1 -0
- package/dist/workflows/shared/schema.d.ts +139 -0
- package/dist/workflows/shared/schema.d.ts.map +1 -0
- package/dist/workflows/task-planning/prompts.d.ts +37 -0
- package/dist/workflows/task-planning/prompts.d.ts.map +1 -0
- package/dist/workflows/task-planning/schema.d.ts +548 -0
- package/dist/workflows/task-planning/schema.d.ts.map +1 -0
- package/dist/workflows/task-planning/task-planning.d.ts +992 -0
- package/dist/workflows/task-planning/task-planning.d.ts.map +1 -0
- package/dist/workflows/template-builder/template-builder.d.ts +1910 -0
- package/dist/workflows/template-builder/template-builder.d.ts.map +1 -0
- package/dist/workflows/workflow-builder/prompts.d.ts +44 -0
- package/dist/workflows/workflow-builder/prompts.d.ts.map +1 -0
- package/dist/workflows/workflow-builder/schema.d.ts +1170 -0
- package/dist/workflows/workflow-builder/schema.d.ts.map +1 -0
- package/dist/workflows/workflow-builder/tools.d.ts +309 -0
- package/dist/workflows/workflow-builder/tools.d.ts.map +1 -0
- package/dist/workflows/workflow-builder/workflow-builder.d.ts +2714 -0
- package/dist/workflows/workflow-builder/workflow-builder.d.ts.map +1 -0
- package/dist/workflows/workflow-map.d.ts +3735 -0
- package/dist/workflows/workflow-map.d.ts.map +1 -0
- package/package.json +20 -9
- package/.turbo/turbo-build.log +0 -12
- package/dist/_tsup-dts-rollup.d.cts +0 -14933
- package/dist/_tsup-dts-rollup.d.ts +0 -14933
- package/dist/index.cjs +0 -4357
- package/dist/index.d.cts +0 -4
- package/eslint.config.js +0 -11
- package/integration-tests/CHANGELOG.md +0 -9
- package/integration-tests/README.md +0 -154
- package/integration-tests/docker-compose.yml +0 -39
- package/integration-tests/package.json +0 -38
- package/integration-tests/src/agent-template-behavior.test.ts +0 -103
- package/integration-tests/src/fixtures/minimal-mastra-project/env.example +0 -6
- package/integration-tests/src/fixtures/minimal-mastra-project/package.json +0 -17
- package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/agents/weather.ts +0 -34
- package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/index.ts +0 -15
- package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/mcp/index.ts +0 -46
- package/integration-tests/src/fixtures/minimal-mastra-project/src/mastra/tools/weather.ts +0 -14
- package/integration-tests/src/fixtures/minimal-mastra-project/tsconfig.json +0 -17
- package/integration-tests/src/template-integration.test.ts +0 -312
- package/integration-tests/tsconfig.json +0 -9
- package/integration-tests/vitest.config.ts +0 -18
- package/src/agent/index.ts +0 -187
- package/src/agent-builder.test.ts +0 -313
- package/src/defaults.ts +0 -2876
- package/src/index.ts +0 -3
- package/src/processors/tool-summary.ts +0 -145
- package/src/processors/write-file.ts +0 -17
- package/src/types.ts +0 -305
- package/src/utils.ts +0 -409
- package/src/workflows/index.ts +0 -1
- package/src/workflows/template-builder.ts +0 -1682
- package/tsconfig.json +0 -5
- package/vitest.config.ts +0 -11
package/src/defaults.ts
DELETED
|
@@ -1,2876 +0,0 @@
|
|
|
1
|
-
import { spawn as nodeSpawn } from 'child_process';
|
|
2
|
-
import { readFile, writeFile, mkdir, stat, readdir } from 'fs/promises';
|
|
3
|
-
import { join, dirname, relative, isAbsolute, resolve } from 'path';
|
|
4
|
-
import { createTool } from '@mastra/core/tools';
|
|
5
|
-
import ignore from 'ignore';
|
|
6
|
-
import { z } from 'zod';
|
|
7
|
-
import { exec, execFile, spawnSWPM, spawnWithOutput } from './utils';
|
|
8
|
-
|
|
9
|
-
export class AgentBuilderDefaults {
|
|
10
|
-
static DEFAULT_INSTRUCTIONS = (
|
|
11
|
-
projectPath?: string,
|
|
12
|
-
) => `You are a Mastra Expert Agent, specialized in building production-ready AI applications using the Mastra framework. You excel at creating agents, tools, workflows, and complete applications with real, working implementations.
|
|
13
|
-
|
|
14
|
-
## Core Identity & Capabilities
|
|
15
|
-
|
|
16
|
-
**Primary Role:** Transform natural language requirements into working Mastra applications
|
|
17
|
-
**Key Strength:** Deep knowledge of Mastra patterns, conventions, and best practices
|
|
18
|
-
**Output Quality:** Production-ready code that follows Mastra ecosystem standards
|
|
19
|
-
|
|
20
|
-
## Workflow: The MASTRA Method
|
|
21
|
-
|
|
22
|
-
Follow this sequence for every coding task:
|
|
23
|
-
|
|
24
|
-
IF NO PROJECT EXISTS, USE THE MANAGEPROJECT TOOL TO CREATE A NEW PROJECT
|
|
25
|
-
|
|
26
|
-
DO NOT INCLUDE TODOS IN THE CODE, UNLESS SPECIFICALLY ASKED TO DO SO, CREATE REAL WORLD CODE
|
|
27
|
-
|
|
28
|
-
### 1. 🔍 **UNDERSTAND** (Information Gathering)
|
|
29
|
-
- **Explore Mastra Docs**: Use docs tools to understand relevant Mastra patterns and APIs
|
|
30
|
-
- **Analyze Project**: Use file exploration to understand existing codebase structure
|
|
31
|
-
- **Web Research**: Search for packages, examples, or solutions when docs are insufficient
|
|
32
|
-
- **Clarify Requirements**: Ask targeted questions only when critical information is missing
|
|
33
|
-
|
|
34
|
-
### 2. 📋 **PLAN** (Strategy & Design)
|
|
35
|
-
- **Architecture**: Design using Mastra conventions (agents, tools, workflows, memory)
|
|
36
|
-
- **Dependencies**: Identify required packages and Mastra components
|
|
37
|
-
- **Integration**: Plan how to integrate with existing project structure
|
|
38
|
-
- **Validation**: Define how to test and verify the implementation
|
|
39
|
-
|
|
40
|
-
### 3. 🛠️ **BUILD** (Implementation)
|
|
41
|
-
- **Install First**: Use \`manageProject\` tool to install required packages
|
|
42
|
-
- **Follow Patterns**: Implement using established Mastra conventions
|
|
43
|
-
- **Real Code Only**: Build actual working functionality, never mock implementations
|
|
44
|
-
- **Environment Setup**: Create proper .env configuration and documentation
|
|
45
|
-
|
|
46
|
-
### 4. ✅ **VALIDATE** (Quality Assurance)
|
|
47
|
-
- **Code Validation**: Run \`validateCode\` with types and lint checks
|
|
48
|
-
- **Testing**: Execute tests if available
|
|
49
|
-
- **Server Testing**: Use \`manageServer\` and \`httpRequest\` for API validation
|
|
50
|
-
- **Fix Issues**: Address all errors before completion
|
|
51
|
-
|
|
52
|
-
## Mastra-Specific Guidelines
|
|
53
|
-
|
|
54
|
-
### Framework Knowledge
|
|
55
|
-
- **Agents**: Use \`@mastra/core/agent\` with proper configuration
|
|
56
|
-
- **Tools**: Create tools with \`@mastra/core/tools\` and proper schemas
|
|
57
|
-
- **Memory**: Implement memory with \`@mastra/memory\` and appropriate processors
|
|
58
|
-
- **Workflows**: Build workflows with \`@mastra/core/workflows\`
|
|
59
|
-
- **Integrations**: Leverage Mastra's extensive integration ecosystem
|
|
60
|
-
|
|
61
|
-
### Code Standards
|
|
62
|
-
- **TypeScript First**: All code must be properly typed
|
|
63
|
-
- **Zod Schemas**: Use Zod for all data validation
|
|
64
|
-
- **Environment Variables**: Proper .env configuration with examples
|
|
65
|
-
- **Error Handling**: Comprehensive error handling with meaningful messages
|
|
66
|
-
- **Security**: Never expose credentials or sensitive data
|
|
67
|
-
|
|
68
|
-
### Project Structure
|
|
69
|
-
- Follow Mastra project conventions (\`src/mastra/\`, config files)
|
|
70
|
-
- Use proper file organization (agents, tools, workflows in separate directories)
|
|
71
|
-
- Maintain consistent naming conventions
|
|
72
|
-
- Include proper exports and imports
|
|
73
|
-
|
|
74
|
-
## Communication Style
|
|
75
|
-
|
|
76
|
-
**Conciseness**: Keep responses focused and actionable
|
|
77
|
-
**Clarity**: Explain complex concepts in simple terms
|
|
78
|
-
**Directness**: State what you're doing and why
|
|
79
|
-
**No Fluff**: Avoid unnecessary explanations or apologies
|
|
80
|
-
|
|
81
|
-
### Response Format
|
|
82
|
-
1. **Brief Status**: One line stating what you're doing
|
|
83
|
-
2. **Tool Usage**: Execute necessary tools
|
|
84
|
-
3. **Results Summary**: Concise summary of what was accomplished
|
|
85
|
-
4. **Next Steps**: Clear indication of completion or next actions
|
|
86
|
-
|
|
87
|
-
## Tool Usage Strategy
|
|
88
|
-
|
|
89
|
-
### File Operations
|
|
90
|
-
- **Project-Relative Paths**: All file paths are resolved relative to the project directory (unless absolute paths are used)
|
|
91
|
-
- **Read First**: Always read files before editing to understand context
|
|
92
|
-
- **Precise Edits**: Use exact text matching for search/replace operations
|
|
93
|
-
- **Batch Operations**: Group related file operations when possible
|
|
94
|
-
|
|
95
|
-
### Project Management
|
|
96
|
-
- **manageProject**: Use for package installation, project creation, dependency management
|
|
97
|
-
- **validateCode**: Always run after code changes to ensure quality
|
|
98
|
-
- **manageServer**: Use for testing Mastra server functionality
|
|
99
|
-
- **httpRequest**: Test API endpoints and integrations
|
|
100
|
-
|
|
101
|
-
### Information Gathering
|
|
102
|
-
- **Mastra Docs**: Primary source for Mastra-specific information
|
|
103
|
-
- **Web Search**: Secondary source for packages and external solutions
|
|
104
|
-
- **File Exploration**: Understand existing project structure and patterns
|
|
105
|
-
|
|
106
|
-
## Error Handling & Recovery
|
|
107
|
-
|
|
108
|
-
### Validation Failures
|
|
109
|
-
- Fix TypeScript errors immediately
|
|
110
|
-
- Address linting issues systematically
|
|
111
|
-
- Re-validate until clean
|
|
112
|
-
|
|
113
|
-
### Build Issues
|
|
114
|
-
- Check dependencies and versions
|
|
115
|
-
- Verify Mastra configuration
|
|
116
|
-
- Test in isolation when needed
|
|
117
|
-
|
|
118
|
-
### Integration Problems
|
|
119
|
-
- Verify API keys and environment setup
|
|
120
|
-
- Test connections independently
|
|
121
|
-
- Debug with logging and error messages
|
|
122
|
-
|
|
123
|
-
## Security & Best Practices
|
|
124
|
-
|
|
125
|
-
**Never:**
|
|
126
|
-
- Hard-code API keys or secrets
|
|
127
|
-
- Generate mock or placeholder implementations
|
|
128
|
-
- Skip error handling
|
|
129
|
-
- Ignore TypeScript errors
|
|
130
|
-
- Create insecure code patterns
|
|
131
|
-
- ask for file paths, you should be able to use the provided tools to explore the file system
|
|
132
|
-
|
|
133
|
-
**Always:**
|
|
134
|
-
- Use environment variables for configuration
|
|
135
|
-
- Implement proper input validation
|
|
136
|
-
- Follow security best practices
|
|
137
|
-
- Create complete, working implementations
|
|
138
|
-
- Test thoroughly before completion
|
|
139
|
-
|
|
140
|
-
## Output Requirements
|
|
141
|
-
|
|
142
|
-
### Code Quality
|
|
143
|
-
- ✅ TypeScript compilation passes
|
|
144
|
-
- ✅ ESLint validation passes
|
|
145
|
-
- ✅ Proper error handling implemented
|
|
146
|
-
- ✅ Environment variables configured
|
|
147
|
-
- ✅ Tests included when appropriate
|
|
148
|
-
|
|
149
|
-
### Documentation
|
|
150
|
-
- ✅ Clear setup instructions
|
|
151
|
-
- ✅ Environment variable documentation
|
|
152
|
-
- ✅ Usage examples provided
|
|
153
|
-
- ✅ API documentation for custom tools
|
|
154
|
-
|
|
155
|
-
### Integration
|
|
156
|
-
- ✅ Follows Mastra conventions
|
|
157
|
-
- ✅ Integrates with existing project
|
|
158
|
-
- ✅ Proper imports and exports
|
|
159
|
-
- ✅ Compatible with Mastra ecosystem
|
|
160
|
-
|
|
161
|
-
## Project Context
|
|
162
|
-
|
|
163
|
-
**Working Directory**: ${projectPath}
|
|
164
|
-
**Focus**: Mastra framework applications
|
|
165
|
-
**Goal**: Production-ready implementations
|
|
166
|
-
|
|
167
|
-
Remember: You are building real applications, not prototypes. Every implementation should be complete, secure, and ready for production use.
|
|
168
|
-
|
|
169
|
-
## Enhanced Tool Set
|
|
170
|
-
|
|
171
|
-
You have access to an enhanced set of tools based on production coding agent patterns:
|
|
172
|
-
|
|
173
|
-
### Task Management
|
|
174
|
-
- **taskManager**: Create and track multi-step coding tasks with states (pending, in_progress, completed, blocked). Use this for complex projects that require systematic progress tracking.
|
|
175
|
-
|
|
176
|
-
### Code Discovery & Analysis
|
|
177
|
-
- **codeAnalyzer**: Analyze codebase structure, discover definitions (functions, classes, interfaces), map dependencies, and understand architectural patterns.
|
|
178
|
-
- **smartSearch**: Intelligent search with context awareness, pattern matching, and relevance scoring.
|
|
179
|
-
|
|
180
|
-
### Advanced File Operations
|
|
181
|
-
- **readFile**: Read files with optional line ranges, encoding support, metadata
|
|
182
|
-
- **writeFile**: Write files with directory creation
|
|
183
|
-
- **listDirectory**: Directory listing with filtering, recursion, metadata
|
|
184
|
-
- **multiEdit**: Perform multiple search-replace operations across files atomically with backup creation
|
|
185
|
-
- **executeCommand**: Execute shell commands with proper error handling and working directory support
|
|
186
|
-
|
|
187
|
-
**Important**: All file paths are resolved relative to the project directory unless absolute paths are provided.
|
|
188
|
-
|
|
189
|
-
### Communication & Workflow
|
|
190
|
-
- **askClarification**: Ask users for clarification when requirements are unclear or multiple options exist.
|
|
191
|
-
- **attemptCompletion**: Signal task completion with validation status and confidence metrics.
|
|
192
|
-
|
|
193
|
-
### Guidelines for Enhanced Tools:
|
|
194
|
-
|
|
195
|
-
1. **Use taskManager proactively** for any task requiring 3+ steps or complex coordination
|
|
196
|
-
2. **Start with codeAnalyzer** when working with unfamiliar codebases to understand structure
|
|
197
|
-
3. **Use smartSearch** for intelligent pattern discovery across the codebase
|
|
198
|
-
4. **Apply multiEdit** for systematic refactoring across multiple files
|
|
199
|
-
5. **Ask for clarification** when requirements are ambiguous rather than making assumptions
|
|
200
|
-
6. **Signal completion** with comprehensive summaries and validation status
|
|
201
|
-
|
|
202
|
-
Use the following basic examples to guide your implementation.
|
|
203
|
-
|
|
204
|
-
<examples>
|
|
205
|
-
### Weather Agent
|
|
206
|
-
\`\`\`
|
|
207
|
-
// ./src/agents/weather-agent.ts
|
|
208
|
-
import { openai } from '@ai-sdk/openai';
|
|
209
|
-
import { Agent } from '@mastra/core/agent';
|
|
210
|
-
import { Memory } from '@mastra/memory';
|
|
211
|
-
import { LibSQLStore } from '@mastra/libsql';
|
|
212
|
-
import { weatherTool } from '../tools/weather-tool';
|
|
213
|
-
|
|
214
|
-
export const weatherAgent = new Agent({
|
|
215
|
-
name: 'Weather Agent',
|
|
216
|
-
instructions: \${instructions},
|
|
217
|
-
model: openai('gpt-4o-mini'),
|
|
218
|
-
tools: { weatherTool },
|
|
219
|
-
memory: new Memory({
|
|
220
|
-
storage: new LibSQLStore({
|
|
221
|
-
url: 'file:../mastra.db', // ask user what database to use, use this as the default
|
|
222
|
-
}),
|
|
223
|
-
}),
|
|
224
|
-
});
|
|
225
|
-
\`\`\`
|
|
226
|
-
|
|
227
|
-
### Weather Tool
|
|
228
|
-
\`\`\`
|
|
229
|
-
// ./src/tools/weather-tool.ts
|
|
230
|
-
import { createTool } from '@mastra/core/tools';
|
|
231
|
-
import { z } from 'zod';
|
|
232
|
-
import { getWeather } from '../tools/weather-tool';
|
|
233
|
-
|
|
234
|
-
export const weatherTool = createTool({
|
|
235
|
-
id: 'get-weather',
|
|
236
|
-
description: 'Get current weather for a location',
|
|
237
|
-
inputSchema: z.object({
|
|
238
|
-
location: z.string().describe('City name'),
|
|
239
|
-
}),
|
|
240
|
-
outputSchema: z.object({
|
|
241
|
-
temperature: z.number(),
|
|
242
|
-
feelsLike: z.number(),
|
|
243
|
-
humidity: z.number(),
|
|
244
|
-
windSpeed: z.number(),
|
|
245
|
-
windGust: z.number(),
|
|
246
|
-
conditions: z.string(),
|
|
247
|
-
location: z.string(),
|
|
248
|
-
}),
|
|
249
|
-
execute: async ({ context }) => {
|
|
250
|
-
return await getWeather(context.location);
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
\`\`\`
|
|
254
|
-
|
|
255
|
-
### Weather Workflow
|
|
256
|
-
\`\`\`
|
|
257
|
-
// ./src/workflows/weather-workflow.ts
|
|
258
|
-
import { createStep, createWorkflow } from '@mastra/core/workflows';
|
|
259
|
-
import { z } from 'zod';
|
|
260
|
-
|
|
261
|
-
const fetchWeather = createStep({
|
|
262
|
-
id: 'fetch-weather',
|
|
263
|
-
description: 'Fetches weather forecast for a given city',
|
|
264
|
-
inputSchema: z.object({
|
|
265
|
-
city: z.string().describe('The city to get the weather for'),
|
|
266
|
-
}),
|
|
267
|
-
outputSchema: forecastSchema,
|
|
268
|
-
execute: async ({ inputData }) => {
|
|
269
|
-
if (!inputData) {
|
|
270
|
-
throw new Error('Input data not found');
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const geocodingUrl = \`https://geocoding-api.open-meteo.com/v1/search?name=\${encodeURIComponent(inputData.city)}&count=1\`;
|
|
274
|
-
const geocodingResponse = await fetch(geocodingUrl);
|
|
275
|
-
const geocodingData = (await geocodingResponse.json()) as {
|
|
276
|
-
results: { latitude: number; longitude: number; name: string }[];
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
if (!geocodingData.results?.[0]) {
|
|
280
|
-
throw new Error(\`Location '\${inputData.city}' not found\`);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const { latitude, longitude, name } = geocodingData.results[0];
|
|
284
|
-
|
|
285
|
-
const weatherUrl = \`https://api.open-meteo.com/v1/forecast?latitude=\${latitude}&longitude=\${longitude}¤t=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m\`
|
|
286
|
-
const response = await fetch(weatherUrl);
|
|
287
|
-
const data = (await response.json()) as {
|
|
288
|
-
current: {
|
|
289
|
-
time: string;
|
|
290
|
-
precipitation: number;
|
|
291
|
-
weathercode: number;
|
|
292
|
-
};
|
|
293
|
-
hourly: {
|
|
294
|
-
precipitation_probability: number[];
|
|
295
|
-
temperature_2m: number[];
|
|
296
|
-
};
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const forecast = {
|
|
300
|
-
date: new Date().toISOString(),
|
|
301
|
-
maxTemp: Math.max(...data.hourly.temperature_2m),
|
|
302
|
-
minTemp: Math.min(...data.hourly.temperature_2m),
|
|
303
|
-
condition: getWeatherCondition(data.current.weathercode),
|
|
304
|
-
precipitationChance: data.hourly.precipitation_probability.reduce(
|
|
305
|
-
(acc, curr) => Math.max(acc, curr),
|
|
306
|
-
0,
|
|
307
|
-
),
|
|
308
|
-
location: name,
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
return forecast;
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
const planActivities = createStep({
|
|
316
|
-
id: 'plan-activities',
|
|
317
|
-
description: 'Suggests activities based on weather conditions',
|
|
318
|
-
inputSchema: forecastSchema,
|
|
319
|
-
outputSchema: z.object({
|
|
320
|
-
activities: z.string(),
|
|
321
|
-
}),
|
|
322
|
-
execute: async ({ inputData, mastra }) => {
|
|
323
|
-
const forecast = inputData;
|
|
324
|
-
|
|
325
|
-
if (!forecast) {
|
|
326
|
-
throw new Error('Forecast data not found');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const agent = mastra?.getAgent('weatherAgent');
|
|
330
|
-
if (!agent) {
|
|
331
|
-
throw new Error('Weather agent not found');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const prompt = \${weatherWorkflowPrompt}
|
|
335
|
-
|
|
336
|
-
const response = await agent.stream([
|
|
337
|
-
{
|
|
338
|
-
role: 'user',
|
|
339
|
-
content: prompt,
|
|
340
|
-
},
|
|
341
|
-
]);
|
|
342
|
-
|
|
343
|
-
let activitiesText = '';
|
|
344
|
-
|
|
345
|
-
for await (const chunk of response.textStream) {
|
|
346
|
-
process.stdout.write(chunk);
|
|
347
|
-
activitiesText += chunk;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return {
|
|
351
|
-
activities: activitiesText,
|
|
352
|
-
};
|
|
353
|
-
},
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const weatherWorkflow = createWorkflow({
|
|
357
|
-
id: 'weather-workflow',
|
|
358
|
-
inputSchema: z.object({
|
|
359
|
-
city: z.string().describe('The city to get the weather for'),
|
|
360
|
-
}),
|
|
361
|
-
outputSchema: z.object({
|
|
362
|
-
activities: z.string(),
|
|
363
|
-
}),
|
|
364
|
-
})
|
|
365
|
-
.then(fetchWeather)
|
|
366
|
-
.then(planActivities);
|
|
367
|
-
|
|
368
|
-
weatherWorkflow.commit();
|
|
369
|
-
\`\`\`
|
|
370
|
-
export { weatherWorkflow };
|
|
371
|
-
\`\`\`
|
|
372
|
-
|
|
373
|
-
### Mastra instance
|
|
374
|
-
\`\`\`
|
|
375
|
-
// ./src/mastra.ts
|
|
376
|
-
|
|
377
|
-
import { Mastra } from '@mastra/core/mastra';
|
|
378
|
-
import { PinoLogger } from '@mastra/loggers';
|
|
379
|
-
import { LibSQLStore } from '@mastra/libsql';
|
|
380
|
-
import { weatherWorkflow } from './workflows/weather-workflow';
|
|
381
|
-
import { weatherAgent } from './agents/weather-agent';
|
|
382
|
-
|
|
383
|
-
export const mastra = new Mastra({
|
|
384
|
-
workflows: { weatherWorkflow },
|
|
385
|
-
agents: { weatherAgent },
|
|
386
|
-
storage: new LibSQLStore({
|
|
387
|
-
// stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db
|
|
388
|
-
url: ":memory:",
|
|
389
|
-
}),
|
|
390
|
-
logger: new PinoLogger({
|
|
391
|
-
name: 'Mastra',
|
|
392
|
-
level: 'info',
|
|
393
|
-
}),
|
|
394
|
-
});
|
|
395
|
-
\`\`\`
|
|
396
|
-
|
|
397
|
-
</examples>`;
|
|
398
|
-
|
|
399
|
-
static DEFAULT_MEMORY_CONFIG = {
|
|
400
|
-
lastMessages: 20,
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
static DEFAULT_FOLDER_STRUCTURE = {
|
|
404
|
-
agent: 'src/mastra/agents',
|
|
405
|
-
workflow: 'src/mastra/workflows',
|
|
406
|
-
tool: 'src/mastra/tools',
|
|
407
|
-
'mcp-server': 'src/mastra/mcp',
|
|
408
|
-
network: 'src/mastra/networks',
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
static DEFAULT_TOOLS = async (projectPath: string, mode: 'template' | 'code-editor' = 'code-editor') => {
|
|
412
|
-
const agentBuilderTools = {
|
|
413
|
-
readFile: createTool({
|
|
414
|
-
id: 'read-file',
|
|
415
|
-
description: 'Read contents of a file with optional line range selection.',
|
|
416
|
-
inputSchema: z.object({
|
|
417
|
-
filePath: z.string().describe('Path to the file to read'),
|
|
418
|
-
startLine: z.number().optional().describe('Starting line number (1-indexed)'),
|
|
419
|
-
endLine: z.number().optional().describe('Ending line number (1-indexed, inclusive)'),
|
|
420
|
-
encoding: z.string().default('utf-8').describe('File encoding'),
|
|
421
|
-
}),
|
|
422
|
-
outputSchema: z.object({
|
|
423
|
-
success: z.boolean(),
|
|
424
|
-
content: z.string().optional(),
|
|
425
|
-
lines: z.array(z.string()).optional(),
|
|
426
|
-
metadata: z
|
|
427
|
-
.object({
|
|
428
|
-
size: z.number(),
|
|
429
|
-
totalLines: z.number(),
|
|
430
|
-
encoding: z.string(),
|
|
431
|
-
lastModified: z.string(),
|
|
432
|
-
})
|
|
433
|
-
.optional(),
|
|
434
|
-
error: z.string().optional(),
|
|
435
|
-
}),
|
|
436
|
-
execute: async ({ context }) => {
|
|
437
|
-
return await AgentBuilderDefaults.readFile({ ...context, projectPath });
|
|
438
|
-
},
|
|
439
|
-
}),
|
|
440
|
-
|
|
441
|
-
writeFile: createTool({
|
|
442
|
-
id: 'write-file',
|
|
443
|
-
description: 'Write content to a file, with options for creating directories.',
|
|
444
|
-
inputSchema: z.object({
|
|
445
|
-
filePath: z.string().describe('Path to the file to write'),
|
|
446
|
-
content: z.string().describe('Content to write to the file'),
|
|
447
|
-
createDirs: z.boolean().default(true).describe("Create parent directories if they don't exist"),
|
|
448
|
-
encoding: z.string().default('utf-8').describe('File encoding'),
|
|
449
|
-
}),
|
|
450
|
-
outputSchema: z.object({
|
|
451
|
-
success: z.boolean(),
|
|
452
|
-
filePath: z.string(),
|
|
453
|
-
bytesWritten: z.number().optional(),
|
|
454
|
-
message: z.string(),
|
|
455
|
-
error: z.string().optional(),
|
|
456
|
-
}),
|
|
457
|
-
execute: async ({ context }) => {
|
|
458
|
-
return await AgentBuilderDefaults.writeFile({ ...context, projectPath });
|
|
459
|
-
},
|
|
460
|
-
}),
|
|
461
|
-
|
|
462
|
-
listDirectory: createTool({
|
|
463
|
-
id: 'list-directory',
|
|
464
|
-
description: 'List contents of a directory with filtering and metadata options.',
|
|
465
|
-
inputSchema: z.object({
|
|
466
|
-
path: z.string().describe('Directory path to list'),
|
|
467
|
-
recursive: z.boolean().default(false).describe('List subdirectories recursively'),
|
|
468
|
-
includeHidden: z.boolean().default(false).describe('Include hidden files and directories'),
|
|
469
|
-
pattern: z.string().optional().describe('Glob pattern to filter files'),
|
|
470
|
-
maxDepth: z.number().default(10).describe('Maximum recursion depth'),
|
|
471
|
-
includeMetadata: z.boolean().default(true).describe('Include file metadata'),
|
|
472
|
-
}),
|
|
473
|
-
outputSchema: z.object({
|
|
474
|
-
success: z.boolean(),
|
|
475
|
-
items: z.array(
|
|
476
|
-
z.object({
|
|
477
|
-
name: z.string(),
|
|
478
|
-
path: z.string(),
|
|
479
|
-
type: z.enum(['file', 'directory', 'symlink']),
|
|
480
|
-
size: z.number().optional(),
|
|
481
|
-
lastModified: z.string().optional(),
|
|
482
|
-
permissions: z.string().optional(),
|
|
483
|
-
}),
|
|
484
|
-
),
|
|
485
|
-
totalItems: z.number(),
|
|
486
|
-
path: z.string(),
|
|
487
|
-
message: z.string(),
|
|
488
|
-
error: z.string().optional(),
|
|
489
|
-
}),
|
|
490
|
-
execute: async ({ context }) => {
|
|
491
|
-
return await AgentBuilderDefaults.listDirectory({ ...context, projectPath });
|
|
492
|
-
},
|
|
493
|
-
}),
|
|
494
|
-
|
|
495
|
-
executeCommand: createTool({
|
|
496
|
-
id: 'execute-command',
|
|
497
|
-
description: 'Execute shell commands with proper error handling and output capture.',
|
|
498
|
-
inputSchema: z.object({
|
|
499
|
-
command: z.string().describe('Shell command to execute'),
|
|
500
|
-
workingDirectory: z.string().optional().describe('Working directory for command execution'),
|
|
501
|
-
timeout: z.number().default(30000).describe('Timeout in milliseconds'),
|
|
502
|
-
captureOutput: z.boolean().default(true).describe('Capture command output'),
|
|
503
|
-
shell: z.string().optional().describe('Shell to use (defaults to system shell)'),
|
|
504
|
-
env: z.record(z.string()).optional().describe('Environment variables'),
|
|
505
|
-
}),
|
|
506
|
-
outputSchema: z.object({
|
|
507
|
-
success: z.boolean(),
|
|
508
|
-
exitCode: z.number().optional(),
|
|
509
|
-
stdout: z.string().optional(),
|
|
510
|
-
stderr: z.string().optional(),
|
|
511
|
-
command: z.string(),
|
|
512
|
-
workingDirectory: z.string().optional(),
|
|
513
|
-
executionTime: z.number().optional(),
|
|
514
|
-
error: z.string().optional(),
|
|
515
|
-
}),
|
|
516
|
-
execute: async ({ context }) => {
|
|
517
|
-
return await AgentBuilderDefaults.executeCommand({
|
|
518
|
-
...context,
|
|
519
|
-
workingDirectory: context.workingDirectory || projectPath,
|
|
520
|
-
});
|
|
521
|
-
},
|
|
522
|
-
}),
|
|
523
|
-
// Enhanced Task Management (Critical for complex coding tasks)
|
|
524
|
-
taskManager: createTool({
|
|
525
|
-
id: 'task-manager',
|
|
526
|
-
description:
|
|
527
|
-
'Create and manage structured task lists for coding sessions. Use this for complex multi-step tasks to track progress and ensure thoroughness.',
|
|
528
|
-
inputSchema: z.object({
|
|
529
|
-
action: z.enum(['create', 'update', 'list', 'complete', 'remove']).describe('Task management action'),
|
|
530
|
-
tasks: z
|
|
531
|
-
.array(
|
|
532
|
-
z.object({
|
|
533
|
-
id: z.string().describe('Unique task identifier'),
|
|
534
|
-
content: z.string().describe('Task description, optional if just updating the status').optional(),
|
|
535
|
-
status: z.enum(['pending', 'in_progress', 'completed', 'blocked']).describe('Task status'),
|
|
536
|
-
priority: z.enum(['high', 'medium', 'low']).default('medium').describe('Task priority'),
|
|
537
|
-
dependencies: z.array(z.string()).optional().describe('IDs of tasks this depends on'),
|
|
538
|
-
notes: z.string().optional().describe('Additional notes or context'),
|
|
539
|
-
}),
|
|
540
|
-
)
|
|
541
|
-
.optional()
|
|
542
|
-
.describe('Tasks to create or update'),
|
|
543
|
-
taskId: z.string().optional().describe('Specific task ID for single task operations'),
|
|
544
|
-
}),
|
|
545
|
-
outputSchema: z.object({
|
|
546
|
-
success: z.boolean(),
|
|
547
|
-
tasks: z.array(
|
|
548
|
-
z.object({
|
|
549
|
-
id: z.string(),
|
|
550
|
-
content: z.string(),
|
|
551
|
-
status: z.string(),
|
|
552
|
-
priority: z.string(),
|
|
553
|
-
dependencies: z.array(z.string()).optional(),
|
|
554
|
-
notes: z.string().optional(),
|
|
555
|
-
createdAt: z.string(),
|
|
556
|
-
updatedAt: z.string(),
|
|
557
|
-
}),
|
|
558
|
-
),
|
|
559
|
-
message: z.string(),
|
|
560
|
-
}),
|
|
561
|
-
execute: async ({ context }) => {
|
|
562
|
-
return await AgentBuilderDefaults.manageTaskList(context);
|
|
563
|
-
},
|
|
564
|
-
}),
|
|
565
|
-
|
|
566
|
-
// Advanced File Operations
|
|
567
|
-
multiEdit: createTool({
|
|
568
|
-
id: 'multi-edit',
|
|
569
|
-
description: 'Perform multiple search-replace operations on one or more files in a single atomic operation.',
|
|
570
|
-
inputSchema: z.object({
|
|
571
|
-
operations: z
|
|
572
|
-
.array(
|
|
573
|
-
z.object({
|
|
574
|
-
filePath: z.string().describe('Path to the file to edit'),
|
|
575
|
-
edits: z
|
|
576
|
-
.array(
|
|
577
|
-
z.object({
|
|
578
|
-
oldString: z.string().describe('Exact text to replace'),
|
|
579
|
-
newString: z.string().describe('Replacement text'),
|
|
580
|
-
replaceAll: z.boolean().default(false).describe('Replace all occurrences'),
|
|
581
|
-
}),
|
|
582
|
-
)
|
|
583
|
-
.describe('List of edit operations for this file'),
|
|
584
|
-
}),
|
|
585
|
-
)
|
|
586
|
-
.describe('File edit operations to perform'),
|
|
587
|
-
createBackup: z.boolean().default(false).describe('Create backup files before editing'),
|
|
588
|
-
}),
|
|
589
|
-
outputSchema: z.object({
|
|
590
|
-
success: z.boolean(),
|
|
591
|
-
results: z.array(
|
|
592
|
-
z.object({
|
|
593
|
-
filePath: z.string(),
|
|
594
|
-
editsApplied: z.number(),
|
|
595
|
-
errors: z.array(z.string()),
|
|
596
|
-
backup: z.string().optional(),
|
|
597
|
-
}),
|
|
598
|
-
),
|
|
599
|
-
message: z.string(),
|
|
600
|
-
}),
|
|
601
|
-
execute: async ({ context }) => {
|
|
602
|
-
return await AgentBuilderDefaults.performMultiEdit({ ...context, projectPath });
|
|
603
|
-
},
|
|
604
|
-
}),
|
|
605
|
-
|
|
606
|
-
replaceLines: createTool({
|
|
607
|
-
id: 'replace-lines',
|
|
608
|
-
description:
|
|
609
|
-
'Replace specific line ranges in files with new content. Perfect for fixing multiline imports, function signatures, or other structured code.',
|
|
610
|
-
inputSchema: z.object({
|
|
611
|
-
filePath: z.string().describe('Path to the file to edit'),
|
|
612
|
-
startLine: z.number().describe('Starting line number to replace (1-indexed)'),
|
|
613
|
-
endLine: z.number().describe('Ending line number to replace (1-indexed, inclusive)'),
|
|
614
|
-
newContent: z.string().describe('New content to replace the lines with'),
|
|
615
|
-
createBackup: z.boolean().default(false).describe('Create backup file before editing'),
|
|
616
|
-
}),
|
|
617
|
-
outputSchema: z.object({
|
|
618
|
-
success: z.boolean(),
|
|
619
|
-
message: z.string(),
|
|
620
|
-
linesReplaced: z.number().optional(),
|
|
621
|
-
backup: z.string().optional(),
|
|
622
|
-
error: z.string().optional(),
|
|
623
|
-
}),
|
|
624
|
-
execute: async ({ context }) => {
|
|
625
|
-
return await AgentBuilderDefaults.replaceLines({ ...context, projectPath });
|
|
626
|
-
},
|
|
627
|
-
}),
|
|
628
|
-
|
|
629
|
-
// Interactive Communication
|
|
630
|
-
askClarification: createTool({
|
|
631
|
-
id: 'ask-clarification',
|
|
632
|
-
description: 'Ask the user for clarification when requirements are unclear or when multiple options exist.',
|
|
633
|
-
inputSchema: z.object({
|
|
634
|
-
question: z.string().describe('The specific question to ask'),
|
|
635
|
-
options: z
|
|
636
|
-
.array(
|
|
637
|
-
z.object({
|
|
638
|
-
id: z.string(),
|
|
639
|
-
description: z.string(),
|
|
640
|
-
implications: z.string().optional(),
|
|
641
|
-
}),
|
|
642
|
-
)
|
|
643
|
-
.optional()
|
|
644
|
-
.describe('Multiple choice options if applicable'),
|
|
645
|
-
context: z.string().optional().describe('Additional context about why clarification is needed'),
|
|
646
|
-
urgency: z.enum(['low', 'medium', 'high']).default('medium').describe('How urgent the clarification is'),
|
|
647
|
-
}),
|
|
648
|
-
outputSchema: z.object({
|
|
649
|
-
questionId: z.string(),
|
|
650
|
-
question: z.string(),
|
|
651
|
-
options: z
|
|
652
|
-
.array(
|
|
653
|
-
z.object({
|
|
654
|
-
id: z.string(),
|
|
655
|
-
description: z.string(),
|
|
656
|
-
}),
|
|
657
|
-
)
|
|
658
|
-
.optional(),
|
|
659
|
-
awaitingResponse: z.boolean(),
|
|
660
|
-
}),
|
|
661
|
-
execute: async ({ context }) => {
|
|
662
|
-
return await AgentBuilderDefaults.askClarification(context);
|
|
663
|
-
},
|
|
664
|
-
}),
|
|
665
|
-
|
|
666
|
-
// Enhanced Pattern Search
|
|
667
|
-
smartSearch: createTool({
|
|
668
|
-
id: 'smart-search',
|
|
669
|
-
description: 'Intelligent search across codebase with context awareness and pattern matching.',
|
|
670
|
-
inputSchema: z.object({
|
|
671
|
-
query: z.string().describe('Search query or pattern'),
|
|
672
|
-
type: z.enum(['text', 'regex', 'fuzzy', 'semantic']).default('text').describe('Type of search to perform'),
|
|
673
|
-
scope: z
|
|
674
|
-
.object({
|
|
675
|
-
paths: z.array(z.string()).optional().describe('Specific paths to search'),
|
|
676
|
-
fileTypes: z.array(z.string()).optional().describe('File extensions to include'),
|
|
677
|
-
excludePaths: z.array(z.string()).optional().describe('Paths to exclude'),
|
|
678
|
-
maxResults: z.number().default(50).describe('Maximum number of results'),
|
|
679
|
-
})
|
|
680
|
-
.optional(),
|
|
681
|
-
context: z
|
|
682
|
-
.object({
|
|
683
|
-
beforeLines: z.number().default(2).describe('Lines of context before match'),
|
|
684
|
-
afterLines: z.number().default(2).describe('Lines of context after match'),
|
|
685
|
-
includeDefinitions: z.boolean().default(false).describe('Include function/class definitions'),
|
|
686
|
-
})
|
|
687
|
-
.optional(),
|
|
688
|
-
}),
|
|
689
|
-
outputSchema: z.object({
|
|
690
|
-
success: z.boolean(),
|
|
691
|
-
matches: z.array(
|
|
692
|
-
z.object({
|
|
693
|
-
file: z.string(),
|
|
694
|
-
line: z.number(),
|
|
695
|
-
column: z.number().optional(),
|
|
696
|
-
match: z.string(),
|
|
697
|
-
context: z.object({
|
|
698
|
-
before: z.array(z.string()),
|
|
699
|
-
after: z.array(z.string()),
|
|
700
|
-
}),
|
|
701
|
-
relevance: z.number().optional(),
|
|
702
|
-
}),
|
|
703
|
-
),
|
|
704
|
-
summary: z.object({
|
|
705
|
-
totalMatches: z.number(),
|
|
706
|
-
filesSearched: z.number(),
|
|
707
|
-
patterns: z.array(z.string()),
|
|
708
|
-
}),
|
|
709
|
-
}),
|
|
710
|
-
execute: async ({ context }) => {
|
|
711
|
-
return await AgentBuilderDefaults.performSmartSearch(context, projectPath);
|
|
712
|
-
},
|
|
713
|
-
}),
|
|
714
|
-
|
|
715
|
-
validateCode: createTool({
|
|
716
|
-
id: 'validate-code',
|
|
717
|
-
description:
|
|
718
|
-
'Validates generated code through TypeScript compilation, ESLint, schema validation, and other checks',
|
|
719
|
-
inputSchema: z.object({
|
|
720
|
-
projectPath: z.string().optional().describe('Path to the project to validate (defaults to current project)'),
|
|
721
|
-
validationType: z
|
|
722
|
-
.array(z.enum(['types', 'lint', 'schemas', 'tests', 'build']))
|
|
723
|
-
.describe('Types of validation to perform'),
|
|
724
|
-
files: z
|
|
725
|
-
.array(z.string())
|
|
726
|
-
.optional()
|
|
727
|
-
.describe('Specific files to validate (if not provided, validates entire project)'),
|
|
728
|
-
}),
|
|
729
|
-
outputSchema: z.object({
|
|
730
|
-
valid: z.boolean(),
|
|
731
|
-
errors: z.array(
|
|
732
|
-
z.object({
|
|
733
|
-
type: z.enum(['typescript', 'eslint', 'schema', 'test', 'build']),
|
|
734
|
-
severity: z.enum(['error', 'warning', 'info']),
|
|
735
|
-
message: z.string(),
|
|
736
|
-
file: z.string().optional(),
|
|
737
|
-
line: z.number().optional(),
|
|
738
|
-
column: z.number().optional(),
|
|
739
|
-
code: z.string().optional(),
|
|
740
|
-
}),
|
|
741
|
-
),
|
|
742
|
-
summary: z.object({
|
|
743
|
-
totalErrors: z.number(),
|
|
744
|
-
totalWarnings: z.number(),
|
|
745
|
-
validationsPassed: z.array(z.string()),
|
|
746
|
-
validationsFailed: z.array(z.string()),
|
|
747
|
-
}),
|
|
748
|
-
}),
|
|
749
|
-
execute: async ({ context }) => {
|
|
750
|
-
const { projectPath: validationProjectPath, validationType, files } = context;
|
|
751
|
-
const targetPath = validationProjectPath || projectPath;
|
|
752
|
-
return await AgentBuilderDefaults.validateCode({
|
|
753
|
-
projectPath: targetPath,
|
|
754
|
-
validationType,
|
|
755
|
-
files,
|
|
756
|
-
});
|
|
757
|
-
},
|
|
758
|
-
}),
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
if (mode === 'template') {
|
|
762
|
-
return agentBuilderTools;
|
|
763
|
-
} else {
|
|
764
|
-
return {
|
|
765
|
-
...agentBuilderTools,
|
|
766
|
-
|
|
767
|
-
// Web Search (replaces MCP web search)
|
|
768
|
-
webSearch: createTool({
|
|
769
|
-
id: 'web-search',
|
|
770
|
-
description: 'Search the web for current information and return structured results.',
|
|
771
|
-
inputSchema: z.object({
|
|
772
|
-
query: z.string().describe('Search query'),
|
|
773
|
-
maxResults: z.number().default(10).describe('Maximum number of results to return'),
|
|
774
|
-
region: z.string().default('us').describe('Search region/country code'),
|
|
775
|
-
language: z.string().default('en').describe('Search language'),
|
|
776
|
-
includeImages: z.boolean().default(false).describe('Include image results'),
|
|
777
|
-
dateRange: z.enum(['day', 'week', 'month', 'year', 'all']).default('all').describe('Date range filter'),
|
|
778
|
-
}),
|
|
779
|
-
outputSchema: z.object({
|
|
780
|
-
success: z.boolean(),
|
|
781
|
-
query: z.string(),
|
|
782
|
-
results: z.array(
|
|
783
|
-
z.object({
|
|
784
|
-
title: z.string(),
|
|
785
|
-
url: z.string(),
|
|
786
|
-
snippet: z.string(),
|
|
787
|
-
domain: z.string(),
|
|
788
|
-
publishDate: z.string().optional(),
|
|
789
|
-
relevanceScore: z.number().optional(),
|
|
790
|
-
}),
|
|
791
|
-
),
|
|
792
|
-
totalResults: z.number(),
|
|
793
|
-
searchTime: z.number(),
|
|
794
|
-
suggestions: z.array(z.string()).optional(),
|
|
795
|
-
error: z.string().optional(),
|
|
796
|
-
}),
|
|
797
|
-
execute: async ({ context }) => {
|
|
798
|
-
return await AgentBuilderDefaults.webSearch(context);
|
|
799
|
-
},
|
|
800
|
-
}),
|
|
801
|
-
|
|
802
|
-
// Enhanced Code Discovery
|
|
803
|
-
codeAnalyzer: createTool({
|
|
804
|
-
id: 'code-analyzer',
|
|
805
|
-
description: 'Analyze codebase structure, discover definitions, and understand architecture patterns.',
|
|
806
|
-
inputSchema: z.object({
|
|
807
|
-
action: z
|
|
808
|
-
.enum(['definitions', 'dependencies', 'patterns', 'structure'])
|
|
809
|
-
.describe('Type of analysis to perform'),
|
|
810
|
-
path: z.string().describe('Directory or file path to analyze'),
|
|
811
|
-
language: z.string().optional().describe('Programming language filter'),
|
|
812
|
-
depth: z.number().default(3).describe('Directory traversal depth'),
|
|
813
|
-
includeTests: z.boolean().default(false).describe('Include test files in analysis'),
|
|
814
|
-
}),
|
|
815
|
-
outputSchema: z.object({
|
|
816
|
-
success: z.boolean(),
|
|
817
|
-
analysis: z.object({
|
|
818
|
-
definitions: z
|
|
819
|
-
.array(
|
|
820
|
-
z.object({
|
|
821
|
-
name: z.string(),
|
|
822
|
-
type: z.string(),
|
|
823
|
-
file: z.string(),
|
|
824
|
-
line: z.number().optional(),
|
|
825
|
-
scope: z.string().optional(),
|
|
826
|
-
}),
|
|
827
|
-
)
|
|
828
|
-
.optional(),
|
|
829
|
-
dependencies: z
|
|
830
|
-
.array(
|
|
831
|
-
z.object({
|
|
832
|
-
name: z.string(),
|
|
833
|
-
type: z.enum(['import', 'require', 'include']),
|
|
834
|
-
source: z.string(),
|
|
835
|
-
target: z.string(),
|
|
836
|
-
}),
|
|
837
|
-
)
|
|
838
|
-
.optional(),
|
|
839
|
-
patterns: z
|
|
840
|
-
.array(
|
|
841
|
-
z.object({
|
|
842
|
-
pattern: z.string(),
|
|
843
|
-
description: z.string(),
|
|
844
|
-
files: z.array(z.string()),
|
|
845
|
-
}),
|
|
846
|
-
)
|
|
847
|
-
.optional(),
|
|
848
|
-
structure: z
|
|
849
|
-
.object({
|
|
850
|
-
directories: z.number(),
|
|
851
|
-
files: z.number(),
|
|
852
|
-
languages: z.record(z.number()),
|
|
853
|
-
complexity: z.string(),
|
|
854
|
-
})
|
|
855
|
-
.optional(),
|
|
856
|
-
}),
|
|
857
|
-
message: z.string(),
|
|
858
|
-
}),
|
|
859
|
-
execute: async ({ context }) => {
|
|
860
|
-
return await AgentBuilderDefaults.analyzeCode(context);
|
|
861
|
-
},
|
|
862
|
-
}),
|
|
863
|
-
|
|
864
|
-
// Task Completion Signaling
|
|
865
|
-
attemptCompletion: createTool({
|
|
866
|
-
id: 'attempt-completion',
|
|
867
|
-
description: 'Signal that you believe the requested task has been completed and provide a summary.',
|
|
868
|
-
inputSchema: z.object({
|
|
869
|
-
summary: z.string().describe('Summary of what was accomplished'),
|
|
870
|
-
changes: z
|
|
871
|
-
.array(
|
|
872
|
-
z.object({
|
|
873
|
-
type: z.enum([
|
|
874
|
-
'file_created',
|
|
875
|
-
'file_modified',
|
|
876
|
-
'file_deleted',
|
|
877
|
-
'command_executed',
|
|
878
|
-
'dependency_added',
|
|
879
|
-
]),
|
|
880
|
-
description: z.string(),
|
|
881
|
-
path: z.string().optional(),
|
|
882
|
-
}),
|
|
883
|
-
)
|
|
884
|
-
.describe('List of changes made'),
|
|
885
|
-
validation: z
|
|
886
|
-
.object({
|
|
887
|
-
testsRun: z.boolean().default(false),
|
|
888
|
-
buildsSuccessfully: z.boolean().default(false),
|
|
889
|
-
manualTestingRequired: z.boolean().default(false),
|
|
890
|
-
})
|
|
891
|
-
.describe('Validation status'),
|
|
892
|
-
nextSteps: z.array(z.string()).optional().describe('Suggested next steps or follow-up actions'),
|
|
893
|
-
}),
|
|
894
|
-
outputSchema: z.object({
|
|
895
|
-
completionId: z.string(),
|
|
896
|
-
status: z.enum(['completed', 'needs_review', 'needs_testing']),
|
|
897
|
-
summary: z.string(),
|
|
898
|
-
confidence: z.number().min(0).max(100),
|
|
899
|
-
}),
|
|
900
|
-
execute: async ({ context }) => {
|
|
901
|
-
return await AgentBuilderDefaults.signalCompletion(context);
|
|
902
|
-
},
|
|
903
|
-
}),
|
|
904
|
-
|
|
905
|
-
manageProject: createTool({
|
|
906
|
-
id: 'manage-project',
|
|
907
|
-
description:
|
|
908
|
-
'Handles project management including creating project structures, managing dependencies, and package operations.',
|
|
909
|
-
inputSchema: z.object({
|
|
910
|
-
action: z.enum(['create', 'install', 'upgrade']).describe('The action to perform'),
|
|
911
|
-
features: z
|
|
912
|
-
.array(z.string())
|
|
913
|
-
.optional()
|
|
914
|
-
.describe('Mastra features to include (e.g., ["agents", "memory", "workflows"])'),
|
|
915
|
-
packages: z
|
|
916
|
-
.array(
|
|
917
|
-
z.object({
|
|
918
|
-
name: z.string(),
|
|
919
|
-
version: z.string().optional(),
|
|
920
|
-
}),
|
|
921
|
-
)
|
|
922
|
-
.optional()
|
|
923
|
-
.describe('Packages to install/upgrade'),
|
|
924
|
-
}),
|
|
925
|
-
outputSchema: z.object({
|
|
926
|
-
success: z.boolean(),
|
|
927
|
-
installed: z.array(z.string()).optional(),
|
|
928
|
-
upgraded: z.array(z.string()).optional(),
|
|
929
|
-
warnings: z.array(z.string()).optional(),
|
|
930
|
-
message: z.string().optional(),
|
|
931
|
-
details: z.string().optional(),
|
|
932
|
-
error: z.string().optional(),
|
|
933
|
-
}),
|
|
934
|
-
execute: async ({ context }) => {
|
|
935
|
-
const { action, features, packages } = context;
|
|
936
|
-
try {
|
|
937
|
-
switch (action) {
|
|
938
|
-
case 'create':
|
|
939
|
-
return await AgentBuilderDefaults.createMastraProject({
|
|
940
|
-
projectName: projectPath,
|
|
941
|
-
features,
|
|
942
|
-
});
|
|
943
|
-
case 'install':
|
|
944
|
-
if (!packages?.length) {
|
|
945
|
-
return {
|
|
946
|
-
success: false,
|
|
947
|
-
message: 'Packages array is required for install action',
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
return await AgentBuilderDefaults.installPackages({
|
|
951
|
-
packages,
|
|
952
|
-
projectPath,
|
|
953
|
-
});
|
|
954
|
-
case 'upgrade':
|
|
955
|
-
if (!packages?.length) {
|
|
956
|
-
return {
|
|
957
|
-
success: false,
|
|
958
|
-
message: 'Packages array is required for upgrade action',
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
return await AgentBuilderDefaults.upgradePackages({
|
|
962
|
-
packages,
|
|
963
|
-
projectPath,
|
|
964
|
-
});
|
|
965
|
-
// case 'check':
|
|
966
|
-
// return await AgentBuilderDefaults.checkProject({
|
|
967
|
-
// projectPath,
|
|
968
|
-
// });
|
|
969
|
-
default:
|
|
970
|
-
return {
|
|
971
|
-
success: false,
|
|
972
|
-
message: `Unknown action: ${action}`,
|
|
973
|
-
};
|
|
974
|
-
}
|
|
975
|
-
} catch (error) {
|
|
976
|
-
return {
|
|
977
|
-
success: false,
|
|
978
|
-
message: `Error executing ${action}: ${error instanceof Error ? error.message : String(error)}`,
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
},
|
|
982
|
-
}),
|
|
983
|
-
manageServer: createTool({
|
|
984
|
-
id: 'manage-server',
|
|
985
|
-
description:
|
|
986
|
-
'Manages the Mastra server - start, stop, restart, and check status, use the terminal tool to make curl requests to the server. There is an openapi spec for the server at http://localhost:{port}/openapi.json',
|
|
987
|
-
inputSchema: z.object({
|
|
988
|
-
action: z.enum(['start', 'stop', 'restart', 'status']).describe('Server management action'),
|
|
989
|
-
port: z.number().optional().default(4200).describe('Port to run the server on'),
|
|
990
|
-
}),
|
|
991
|
-
outputSchema: z.object({
|
|
992
|
-
success: z.boolean(),
|
|
993
|
-
status: z.enum(['running', 'stopped', 'starting', 'stopping', 'unknown']),
|
|
994
|
-
pid: z.number().optional(),
|
|
995
|
-
port: z.number().optional(),
|
|
996
|
-
url: z.string().optional(),
|
|
997
|
-
message: z.string().optional(),
|
|
998
|
-
stdout: z.array(z.string()).optional().describe('Server output lines captured during startup'),
|
|
999
|
-
error: z.string().optional(),
|
|
1000
|
-
}),
|
|
1001
|
-
execute: async ({ context }) => {
|
|
1002
|
-
const { action, port } = context;
|
|
1003
|
-
try {
|
|
1004
|
-
switch (action) {
|
|
1005
|
-
case 'start':
|
|
1006
|
-
return await AgentBuilderDefaults.startMastraServer({
|
|
1007
|
-
port,
|
|
1008
|
-
projectPath,
|
|
1009
|
-
});
|
|
1010
|
-
case 'stop':
|
|
1011
|
-
return await AgentBuilderDefaults.stopMastraServer({
|
|
1012
|
-
port,
|
|
1013
|
-
projectPath,
|
|
1014
|
-
});
|
|
1015
|
-
case 'restart':
|
|
1016
|
-
const stopResult = await AgentBuilderDefaults.stopMastraServer({
|
|
1017
|
-
port,
|
|
1018
|
-
projectPath,
|
|
1019
|
-
});
|
|
1020
|
-
if (!stopResult.success) {
|
|
1021
|
-
return {
|
|
1022
|
-
success: false,
|
|
1023
|
-
status: 'unknown' as const,
|
|
1024
|
-
message: `Failed to restart: could not stop server on port ${port}`,
|
|
1025
|
-
error: stopResult.error || 'Unknown stop error',
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
1029
|
-
const startResult = await AgentBuilderDefaults.startMastraServer({
|
|
1030
|
-
port,
|
|
1031
|
-
projectPath,
|
|
1032
|
-
});
|
|
1033
|
-
if (!startResult.success) {
|
|
1034
|
-
return {
|
|
1035
|
-
success: false,
|
|
1036
|
-
status: 'stopped' as const,
|
|
1037
|
-
message: `Failed to restart: server stopped successfully but failed to start on port ${port}`,
|
|
1038
|
-
error: startResult.error || 'Unknown start error',
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
return {
|
|
1042
|
-
...startResult,
|
|
1043
|
-
message: `Mastra server restarted successfully on port ${port}`,
|
|
1044
|
-
};
|
|
1045
|
-
case 'status':
|
|
1046
|
-
return await AgentBuilderDefaults.checkMastraServerStatus({
|
|
1047
|
-
port,
|
|
1048
|
-
projectPath,
|
|
1049
|
-
});
|
|
1050
|
-
default:
|
|
1051
|
-
return {
|
|
1052
|
-
success: false,
|
|
1053
|
-
status: 'unknown' as const,
|
|
1054
|
-
message: `Unknown action: ${action}`,
|
|
1055
|
-
};
|
|
1056
|
-
}
|
|
1057
|
-
} catch (error) {
|
|
1058
|
-
return {
|
|
1059
|
-
success: false,
|
|
1060
|
-
status: 'unknown' as const,
|
|
1061
|
-
message: `Error managing server: ${error instanceof Error ? error.message : String(error)}`,
|
|
1062
|
-
};
|
|
1063
|
-
}
|
|
1064
|
-
},
|
|
1065
|
-
}),
|
|
1066
|
-
httpRequest: createTool({
|
|
1067
|
-
id: 'http-request',
|
|
1068
|
-
description: 'Makes HTTP requests to the Mastra server or external APIs for testing and integration',
|
|
1069
|
-
inputSchema: z.object({
|
|
1070
|
-
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).describe('HTTP method'),
|
|
1071
|
-
url: z.string().describe('Full URL or path (if baseUrl provided)'),
|
|
1072
|
-
baseUrl: z.string().optional().describe('Base URL for the server (e.g., http://localhost:4200)'),
|
|
1073
|
-
headers: z.record(z.string()).optional().describe('HTTP headers'),
|
|
1074
|
-
body: z.any().optional().describe('Request body (will be JSON stringified if object)'),
|
|
1075
|
-
timeout: z.number().optional().default(30000).describe('Request timeout in milliseconds'),
|
|
1076
|
-
}),
|
|
1077
|
-
outputSchema: z.object({
|
|
1078
|
-
success: z.boolean(),
|
|
1079
|
-
status: z.number().optional(),
|
|
1080
|
-
statusText: z.string().optional(),
|
|
1081
|
-
headers: z.record(z.string()).optional(),
|
|
1082
|
-
data: z.any().optional(),
|
|
1083
|
-
error: z.string().optional(),
|
|
1084
|
-
url: z.string(),
|
|
1085
|
-
method: z.string(),
|
|
1086
|
-
}),
|
|
1087
|
-
execute: async ({ context }) => {
|
|
1088
|
-
const { method, url, baseUrl, headers, body, timeout } = context;
|
|
1089
|
-
try {
|
|
1090
|
-
return await AgentBuilderDefaults.makeHttpRequest({
|
|
1091
|
-
method,
|
|
1092
|
-
url,
|
|
1093
|
-
baseUrl,
|
|
1094
|
-
headers,
|
|
1095
|
-
body,
|
|
1096
|
-
timeout,
|
|
1097
|
-
});
|
|
1098
|
-
} catch (error) {
|
|
1099
|
-
return {
|
|
1100
|
-
success: false,
|
|
1101
|
-
url: baseUrl ? `${baseUrl}${url}` : url,
|
|
1102
|
-
method,
|
|
1103
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
},
|
|
1107
|
-
}),
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
};
|
|
1111
|
-
|
|
1112
|
-
/**
|
|
1113
|
-
* Create a new Mastra project using create-mastra CLI
|
|
1114
|
-
*/
|
|
1115
|
-
static async createMastraProject({ features, projectName }: { features?: string[]; projectName?: string }) {
|
|
1116
|
-
try {
|
|
1117
|
-
const args = ['pnpx', 'create-mastra@latest', projectName?.replace(/[;&|`$(){}\[\]]/g, '') ?? '', '-l', 'openai'];
|
|
1118
|
-
if (features && features.length > 0) {
|
|
1119
|
-
args.push('--components', features.join(','));
|
|
1120
|
-
}
|
|
1121
|
-
args.push('--example');
|
|
1122
|
-
|
|
1123
|
-
const { stdout, stderr } = await spawnWithOutput(args[0]!, args.slice(1), {});
|
|
1124
|
-
|
|
1125
|
-
return {
|
|
1126
|
-
success: true,
|
|
1127
|
-
projectPath: `./${projectName}`,
|
|
1128
|
-
message: `Successfully created Mastra project: ${projectName}.`,
|
|
1129
|
-
details: stdout,
|
|
1130
|
-
error: stderr,
|
|
1131
|
-
};
|
|
1132
|
-
} catch (error) {
|
|
1133
|
-
console.log(error);
|
|
1134
|
-
return {
|
|
1135
|
-
success: false,
|
|
1136
|
-
message: `Failed to create project: ${error instanceof Error ? error.message : String(error)}`,
|
|
1137
|
-
};
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
/**
|
|
1142
|
-
* Install packages using the detected package manager
|
|
1143
|
-
*/
|
|
1144
|
-
static async installPackages({
|
|
1145
|
-
packages,
|
|
1146
|
-
projectPath,
|
|
1147
|
-
}: {
|
|
1148
|
-
packages: Array<{ name: string; version?: string }>;
|
|
1149
|
-
projectPath?: string;
|
|
1150
|
-
}) {
|
|
1151
|
-
try {
|
|
1152
|
-
console.log('Installing packages:', JSON.stringify(packages, null, 2));
|
|
1153
|
-
|
|
1154
|
-
const packageStrings = packages.map(p => `${p.name}`);
|
|
1155
|
-
|
|
1156
|
-
await spawnSWPM(projectPath || '', 'add', packageStrings);
|
|
1157
|
-
|
|
1158
|
-
return {
|
|
1159
|
-
success: true,
|
|
1160
|
-
installed: packageStrings,
|
|
1161
|
-
message: `Successfully installed ${packages.length} package(s).`,
|
|
1162
|
-
details: '',
|
|
1163
|
-
};
|
|
1164
|
-
} catch (error) {
|
|
1165
|
-
return {
|
|
1166
|
-
success: false,
|
|
1167
|
-
message: `Failed to install packages: ${error instanceof Error ? error.message : String(error)}`,
|
|
1168
|
-
};
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
/**
|
|
1173
|
-
* Upgrade packages using the detected package manager
|
|
1174
|
-
*/
|
|
1175
|
-
static async upgradePackages({
|
|
1176
|
-
packages,
|
|
1177
|
-
projectPath,
|
|
1178
|
-
}: {
|
|
1179
|
-
packages?: Array<{ name: string; version?: string }>;
|
|
1180
|
-
projectPath?: string;
|
|
1181
|
-
}) {
|
|
1182
|
-
try {
|
|
1183
|
-
console.log('Upgrading specific packages:', JSON.stringify(packages, null, 2));
|
|
1184
|
-
|
|
1185
|
-
let packageNames: string[] = [];
|
|
1186
|
-
|
|
1187
|
-
if (packages && packages.length > 0) {
|
|
1188
|
-
packageNames = packages.map(p => `${p.name}`);
|
|
1189
|
-
}
|
|
1190
|
-
await spawnSWPM(projectPath || '', 'upgrade', packageNames);
|
|
1191
|
-
|
|
1192
|
-
return {
|
|
1193
|
-
success: true,
|
|
1194
|
-
upgraded: packages?.map(p => p.name) || ['all packages'],
|
|
1195
|
-
message: `Packages upgraded successfully.`,
|
|
1196
|
-
details: '',
|
|
1197
|
-
};
|
|
1198
|
-
} catch (error) {
|
|
1199
|
-
return {
|
|
1200
|
-
success: false,
|
|
1201
|
-
message: `Failed to upgrade packages: ${error instanceof Error ? error.message : String(error)}`,
|
|
1202
|
-
};
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
// /**
|
|
1207
|
-
// * Check project health and status
|
|
1208
|
-
// */
|
|
1209
|
-
// static async checkProject({ projectPath }: { projectPath?: string }) {
|
|
1210
|
-
// try {
|
|
1211
|
-
// const execOptions = projectPath ? { cwd: projectPath } : {};
|
|
1212
|
-
|
|
1213
|
-
// let hasPackageJson = false;
|
|
1214
|
-
// let hasMastraConfig = false;
|
|
1215
|
-
|
|
1216
|
-
// try {
|
|
1217
|
-
// await exec('test -f package.json', execOptions);
|
|
1218
|
-
// hasPackageJson = true;
|
|
1219
|
-
// } catch {
|
|
1220
|
-
// // ignore
|
|
1221
|
-
// }
|
|
1222
|
-
|
|
1223
|
-
// try {
|
|
1224
|
-
// await exec('test -f mastra.config.* || test -d src/mastra || test -d mastra', execOptions);
|
|
1225
|
-
// hasMastraConfig = true;
|
|
1226
|
-
// } catch {
|
|
1227
|
-
// // ignore
|
|
1228
|
-
// }
|
|
1229
|
-
|
|
1230
|
-
// const warnings: string[] = [];
|
|
1231
|
-
// if (!hasPackageJson) {
|
|
1232
|
-
// warnings.push('No package.json found - this may not be a Node.js project');
|
|
1233
|
-
// }
|
|
1234
|
-
// if (!hasMastraConfig) {
|
|
1235
|
-
// warnings.push('No Mastra configuration found - run "npx create-mastra" to initialize');
|
|
1236
|
-
// }
|
|
1237
|
-
|
|
1238
|
-
// return {
|
|
1239
|
-
// success: true,
|
|
1240
|
-
// message: `Project health check completed for ${projectPath || 'current directory'}`,
|
|
1241
|
-
// warnings,
|
|
1242
|
-
// checks: {
|
|
1243
|
-
// hasPackageJson,
|
|
1244
|
-
// hasMastraConfig,
|
|
1245
|
-
// },
|
|
1246
|
-
// };
|
|
1247
|
-
// } catch (error) {
|
|
1248
|
-
// return {
|
|
1249
|
-
// success: false,
|
|
1250
|
-
// message: `Failed to check project: ${error instanceof Error ? error.message : String(error)}`,
|
|
1251
|
-
// };
|
|
1252
|
-
// }
|
|
1253
|
-
// }
|
|
1254
|
-
|
|
1255
|
-
/**
|
|
1256
|
-
* Start the Mastra server
|
|
1257
|
-
*/
|
|
1258
|
-
static async startMastraServer({
|
|
1259
|
-
port = 4200,
|
|
1260
|
-
projectPath,
|
|
1261
|
-
env = {},
|
|
1262
|
-
}: {
|
|
1263
|
-
port?: number;
|
|
1264
|
-
projectPath?: string;
|
|
1265
|
-
env?: Record<string, string>;
|
|
1266
|
-
}) {
|
|
1267
|
-
try {
|
|
1268
|
-
const serverEnv = { ...process.env, ...env, PORT: port.toString() };
|
|
1269
|
-
const execOptions = {
|
|
1270
|
-
cwd: projectPath || process.cwd(),
|
|
1271
|
-
env: serverEnv,
|
|
1272
|
-
};
|
|
1273
|
-
|
|
1274
|
-
const serverProcess = nodeSpawn('pnpm', ['run', 'dev'], {
|
|
1275
|
-
...execOptions,
|
|
1276
|
-
detached: true,
|
|
1277
|
-
stdio: 'pipe',
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
const stdoutLines: string[] = [];
|
|
1281
|
-
|
|
1282
|
-
const serverStarted = new Promise<any>((resolve, reject) => {
|
|
1283
|
-
const timeout = setTimeout(() => {
|
|
1284
|
-
reject(new Error(`Server startup timeout after 30 seconds. Output: ${stdoutLines.join('\n')}`));
|
|
1285
|
-
}, 30000);
|
|
1286
|
-
|
|
1287
|
-
serverProcess.stdout?.on('data', data => {
|
|
1288
|
-
const output = data.toString();
|
|
1289
|
-
const lines = output.split('\n').filter((line: string) => line.trim());
|
|
1290
|
-
stdoutLines.push(...lines);
|
|
1291
|
-
|
|
1292
|
-
if (output.includes('Mastra API running on port')) {
|
|
1293
|
-
clearTimeout(timeout);
|
|
1294
|
-
resolve({
|
|
1295
|
-
success: true,
|
|
1296
|
-
status: 'running' as const,
|
|
1297
|
-
pid: serverProcess.pid,
|
|
1298
|
-
port,
|
|
1299
|
-
url: `http://localhost:${port}`,
|
|
1300
|
-
message: `Mastra server started successfully on port ${port}`,
|
|
1301
|
-
stdout: stdoutLines,
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
});
|
|
1305
|
-
|
|
1306
|
-
serverProcess.stderr?.on('data', data => {
|
|
1307
|
-
const errorOutput = data.toString();
|
|
1308
|
-
stdoutLines.push(`[STDERR] ${errorOutput}`);
|
|
1309
|
-
clearTimeout(timeout);
|
|
1310
|
-
reject(new Error(`Server startup failed with error: ${errorOutput}`));
|
|
1311
|
-
});
|
|
1312
|
-
|
|
1313
|
-
serverProcess.on('error', error => {
|
|
1314
|
-
clearTimeout(timeout);
|
|
1315
|
-
reject(error);
|
|
1316
|
-
});
|
|
1317
|
-
|
|
1318
|
-
serverProcess.on('exit', (code, signal) => {
|
|
1319
|
-
clearTimeout(timeout);
|
|
1320
|
-
if (code !== 0 && code !== null) {
|
|
1321
|
-
reject(
|
|
1322
|
-
new Error(
|
|
1323
|
-
`Server process exited with code ${code}${signal ? ` (signal: ${signal})` : ''}. Output: ${stdoutLines.join('\n')}`,
|
|
1324
|
-
),
|
|
1325
|
-
);
|
|
1326
|
-
}
|
|
1327
|
-
});
|
|
1328
|
-
});
|
|
1329
|
-
|
|
1330
|
-
return await serverStarted;
|
|
1331
|
-
} catch (error) {
|
|
1332
|
-
return {
|
|
1333
|
-
success: false,
|
|
1334
|
-
status: 'stopped' as const,
|
|
1335
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1336
|
-
};
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
/**
|
|
1341
|
-
* Stop the Mastra server
|
|
1342
|
-
*/
|
|
1343
|
-
static async stopMastraServer({ port = 4200, projectPath: _projectPath }: { port?: number; projectPath?: string }) {
|
|
1344
|
-
// Validate port to ensure it is a safe integer
|
|
1345
|
-
if (typeof port !== 'number' || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
1346
|
-
return {
|
|
1347
|
-
success: false,
|
|
1348
|
-
status: 'error' as const,
|
|
1349
|
-
error: `Invalid port value: ${String(port)}`,
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
try {
|
|
1353
|
-
// Run lsof safely without shell interpretation
|
|
1354
|
-
const { stdout } = await execFile('lsof', ['-ti', String(port)]);
|
|
1355
|
-
// If no output, treat as "No process found"
|
|
1356
|
-
const effectiveStdout = stdout.trim() ? stdout : 'No process found';
|
|
1357
|
-
|
|
1358
|
-
if (!effectiveStdout || effectiveStdout === 'No process found') {
|
|
1359
|
-
return {
|
|
1360
|
-
success: true,
|
|
1361
|
-
status: 'stopped' as const,
|
|
1362
|
-
message: `No Mastra server found running on port ${port}`,
|
|
1363
|
-
};
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
const pids = stdout
|
|
1367
|
-
.trim()
|
|
1368
|
-
.split('\n')
|
|
1369
|
-
.filter(pid => pid.trim());
|
|
1370
|
-
const killedPids: number[] = [];
|
|
1371
|
-
const failedPids: number[] = [];
|
|
1372
|
-
|
|
1373
|
-
for (const pidStr of pids) {
|
|
1374
|
-
const pid = parseInt(pidStr.trim());
|
|
1375
|
-
if (isNaN(pid)) continue;
|
|
1376
|
-
|
|
1377
|
-
try {
|
|
1378
|
-
process.kill(pid, 'SIGTERM');
|
|
1379
|
-
killedPids.push(pid);
|
|
1380
|
-
} catch (e) {
|
|
1381
|
-
failedPids.push(pid);
|
|
1382
|
-
console.warn(`Failed to kill process ${pid}:`, e);
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
// If some processes failed to be killed, still report partial success
|
|
1387
|
-
// but include warning about failed processes
|
|
1388
|
-
|
|
1389
|
-
if (killedPids.length === 0) {
|
|
1390
|
-
return {
|
|
1391
|
-
success: false,
|
|
1392
|
-
status: 'unknown' as const,
|
|
1393
|
-
message: `Failed to stop any processes on port ${port}`,
|
|
1394
|
-
error: `Could not kill PIDs: ${failedPids.join(', ')}`,
|
|
1395
|
-
};
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// Report partial success if some processes were killed but others failed
|
|
1399
|
-
if (failedPids.length > 0) {
|
|
1400
|
-
console.warn(
|
|
1401
|
-
`Killed ${killedPids.length} processes but failed to kill ${failedPids.length} processes: ${failedPids.join(', ')}`,
|
|
1402
|
-
);
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
// Wait a bit and check if processes are still running
|
|
1406
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
1407
|
-
|
|
1408
|
-
try {
|
|
1409
|
-
const { stdout: checkStdoutRaw } = await execFile('lsof', ['-ti', String(port)]);
|
|
1410
|
-
const checkStdout = checkStdoutRaw.trim() ? checkStdoutRaw : 'No process found';
|
|
1411
|
-
if (checkStdout && checkStdout !== 'No process found') {
|
|
1412
|
-
// Force kill remaining processes
|
|
1413
|
-
const remainingPids = checkStdout
|
|
1414
|
-
.trim()
|
|
1415
|
-
.split('\n')
|
|
1416
|
-
.filter(pid => pid.trim());
|
|
1417
|
-
for (const pidStr of remainingPids) {
|
|
1418
|
-
const pid = parseInt(pidStr.trim());
|
|
1419
|
-
if (!isNaN(pid)) {
|
|
1420
|
-
try {
|
|
1421
|
-
process.kill(pid, 'SIGKILL');
|
|
1422
|
-
} catch {
|
|
1423
|
-
// ignore
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
// Final check
|
|
1429
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1430
|
-
const { stdout: finalCheckRaw } = await execFile('lsof', ['-ti', String(port)]);
|
|
1431
|
-
const finalCheck = finalCheckRaw.trim() ? finalCheckRaw : 'No process found';
|
|
1432
|
-
if (finalCheck && finalCheck !== 'No process found') {
|
|
1433
|
-
return {
|
|
1434
|
-
success: false,
|
|
1435
|
-
status: 'unknown' as const,
|
|
1436
|
-
message: `Server processes still running on port ${port} after stop attempts`,
|
|
1437
|
-
error: `Remaining PIDs: ${finalCheck.trim()}`,
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
} catch (error) {
|
|
1442
|
-
console.warn('Failed to verify server stop:', error);
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
return {
|
|
1446
|
-
success: true,
|
|
1447
|
-
status: 'stopped' as const,
|
|
1448
|
-
message: `Mastra server stopped successfully (port ${port}). Killed PIDs: ${killedPids.join(', ')}`,
|
|
1449
|
-
};
|
|
1450
|
-
} catch (error) {
|
|
1451
|
-
return {
|
|
1452
|
-
success: false,
|
|
1453
|
-
status: 'unknown' as const,
|
|
1454
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1455
|
-
};
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
/**
|
|
1460
|
-
* Check Mastra server status
|
|
1461
|
-
*/
|
|
1462
|
-
static async checkMastraServerStatus({
|
|
1463
|
-
port = 4200,
|
|
1464
|
-
projectPath: _projectPath,
|
|
1465
|
-
}: {
|
|
1466
|
-
port?: number;
|
|
1467
|
-
projectPath?: string;
|
|
1468
|
-
}) {
|
|
1469
|
-
try {
|
|
1470
|
-
const controller = new AbortController();
|
|
1471
|
-
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
1472
|
-
|
|
1473
|
-
const response = await fetch(`http://localhost:${port}/health`, {
|
|
1474
|
-
method: 'GET',
|
|
1475
|
-
signal: controller.signal,
|
|
1476
|
-
});
|
|
1477
|
-
|
|
1478
|
-
clearTimeout(timeoutId);
|
|
1479
|
-
|
|
1480
|
-
if (response.ok) {
|
|
1481
|
-
return {
|
|
1482
|
-
success: true,
|
|
1483
|
-
status: 'running' as const,
|
|
1484
|
-
port,
|
|
1485
|
-
url: `http://localhost:${port}`,
|
|
1486
|
-
message: 'Mastra server is running and healthy',
|
|
1487
|
-
};
|
|
1488
|
-
} else {
|
|
1489
|
-
return {
|
|
1490
|
-
success: false,
|
|
1491
|
-
status: 'unknown' as const,
|
|
1492
|
-
port,
|
|
1493
|
-
message: `Server responding but not healthy (status: ${response.status})`,
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
} catch {
|
|
1497
|
-
// Check if process exists on port
|
|
1498
|
-
try {
|
|
1499
|
-
const { stdout } = await execFile('lsof', ['-ti', String(port)]);
|
|
1500
|
-
const effectiveStdout = stdout.trim() ? stdout : 'No process found';
|
|
1501
|
-
const hasProcess = effectiveStdout && effectiveStdout !== 'No process found';
|
|
1502
|
-
|
|
1503
|
-
return {
|
|
1504
|
-
success: Boolean(hasProcess),
|
|
1505
|
-
status: hasProcess ? ('starting' as const) : ('stopped' as const),
|
|
1506
|
-
port,
|
|
1507
|
-
message: hasProcess
|
|
1508
|
-
? 'Server process exists but not responding to health checks'
|
|
1509
|
-
: 'No server process found on specified port',
|
|
1510
|
-
};
|
|
1511
|
-
} catch {
|
|
1512
|
-
return {
|
|
1513
|
-
success: false,
|
|
1514
|
-
status: 'stopped' as const,
|
|
1515
|
-
port,
|
|
1516
|
-
message: 'Server is not running',
|
|
1517
|
-
};
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
/**
|
|
1523
|
-
* Validate code using TypeScript, ESLint, and other tools
|
|
1524
|
-
*/
|
|
1525
|
-
static async validateCode({
|
|
1526
|
-
projectPath,
|
|
1527
|
-
validationType,
|
|
1528
|
-
files,
|
|
1529
|
-
}: {
|
|
1530
|
-
projectPath?: string;
|
|
1531
|
-
validationType: Array<'types' | 'lint' | 'schemas' | 'tests' | 'build'>;
|
|
1532
|
-
files?: string[];
|
|
1533
|
-
}) {
|
|
1534
|
-
const errors: Array<{
|
|
1535
|
-
type: 'typescript' | 'eslint' | 'schema' | 'test' | 'build';
|
|
1536
|
-
severity: 'error' | 'warning' | 'info';
|
|
1537
|
-
message: string;
|
|
1538
|
-
file?: string;
|
|
1539
|
-
line?: number;
|
|
1540
|
-
column?: number;
|
|
1541
|
-
code?: string;
|
|
1542
|
-
}> = [];
|
|
1543
|
-
const validationsPassed: string[] = [];
|
|
1544
|
-
const validationsFailed: string[] = [];
|
|
1545
|
-
|
|
1546
|
-
const execOptions = { cwd: projectPath };
|
|
1547
|
-
|
|
1548
|
-
// TypeScript validation
|
|
1549
|
-
if (validationType.includes('types')) {
|
|
1550
|
-
try {
|
|
1551
|
-
const fileArgs = files?.length ? files : [];
|
|
1552
|
-
// Use execFile for safe argument passing to avoid shell interpretation
|
|
1553
|
-
const args = ['tsc', '--noEmit', ...fileArgs];
|
|
1554
|
-
await execFile('npx', args, execOptions);
|
|
1555
|
-
validationsPassed.push('types');
|
|
1556
|
-
} catch (error: any) {
|
|
1557
|
-
let tsOutput = '';
|
|
1558
|
-
if (error.stdout) {
|
|
1559
|
-
tsOutput = error.stdout;
|
|
1560
|
-
} else if (error.stderr) {
|
|
1561
|
-
tsOutput = error.stderr;
|
|
1562
|
-
} else if (error.message) {
|
|
1563
|
-
tsOutput = error.message;
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
errors.push({
|
|
1567
|
-
type: 'typescript',
|
|
1568
|
-
severity: 'error',
|
|
1569
|
-
message: tsOutput.trim() || `TypeScript validation failed: ${error.message || String(error)}`,
|
|
1570
|
-
});
|
|
1571
|
-
validationsFailed.push('types');
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
// ESLint validation
|
|
1576
|
-
if (validationType.includes('lint')) {
|
|
1577
|
-
try {
|
|
1578
|
-
const fileArgs = files?.length ? files : ['.'];
|
|
1579
|
-
const eslintArgs = ['eslint', ...fileArgs, '--format', 'json'];
|
|
1580
|
-
const { stdout } = await execFile('npx', eslintArgs, execOptions);
|
|
1581
|
-
|
|
1582
|
-
if (stdout) {
|
|
1583
|
-
const eslintResults = JSON.parse(stdout);
|
|
1584
|
-
const eslintErrors = AgentBuilderDefaults.parseESLintErrors(eslintResults);
|
|
1585
|
-
errors.push(...eslintErrors);
|
|
1586
|
-
|
|
1587
|
-
if (eslintErrors.some(e => e.severity === 'error')) {
|
|
1588
|
-
validationsFailed.push('lint');
|
|
1589
|
-
} else {
|
|
1590
|
-
validationsPassed.push('lint');
|
|
1591
|
-
}
|
|
1592
|
-
} else {
|
|
1593
|
-
validationsPassed.push('lint');
|
|
1594
|
-
}
|
|
1595
|
-
} catch (error: any) {
|
|
1596
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1597
|
-
|
|
1598
|
-
if (errorMessage.includes('"filePath"') || errorMessage.includes('messages')) {
|
|
1599
|
-
try {
|
|
1600
|
-
const eslintResults = JSON.parse(errorMessage);
|
|
1601
|
-
const eslintErrors = AgentBuilderDefaults.parseESLintErrors(eslintResults);
|
|
1602
|
-
errors.push(...eslintErrors);
|
|
1603
|
-
validationsFailed.push('lint');
|
|
1604
|
-
} catch {
|
|
1605
|
-
errors.push({
|
|
1606
|
-
type: 'eslint',
|
|
1607
|
-
severity: 'error',
|
|
1608
|
-
message: `ESLint validation failed: ${errorMessage}`,
|
|
1609
|
-
});
|
|
1610
|
-
validationsFailed.push('lint');
|
|
1611
|
-
}
|
|
1612
|
-
} else {
|
|
1613
|
-
validationsPassed.push('lint');
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
// Build validation
|
|
1619
|
-
// if (validationType.includes('build')) {
|
|
1620
|
-
// try {
|
|
1621
|
-
// await spawnSWPM(execOptions.cwd!, 'build', []);
|
|
1622
|
-
// validationsPassed.push('build');
|
|
1623
|
-
// } catch (error) {
|
|
1624
|
-
// const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1625
|
-
// errors.push({
|
|
1626
|
-
// type: 'build',
|
|
1627
|
-
// severity: 'error',
|
|
1628
|
-
// message: `Build failed: ${errorMessage}`,
|
|
1629
|
-
// });
|
|
1630
|
-
// validationsFailed.push('build');
|
|
1631
|
-
// }
|
|
1632
|
-
// }
|
|
1633
|
-
|
|
1634
|
-
// Test validation
|
|
1635
|
-
// if (validationType.includes('tests')) {
|
|
1636
|
-
// try {
|
|
1637
|
-
// const testCommand = files?.length ? `npx vitest run ${files.join(' ')}` : 'npm test || pnpm test || yarn test';
|
|
1638
|
-
// await exec(testCommand, execOptions);
|
|
1639
|
-
// validationsPassed.push('tests');
|
|
1640
|
-
// } catch (error) {
|
|
1641
|
-
// const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1642
|
-
// errors.push({
|
|
1643
|
-
// type: 'test',
|
|
1644
|
-
// severity: 'error',
|
|
1645
|
-
// message: `Tests failed: ${errorMessage}`,
|
|
1646
|
-
// });
|
|
1647
|
-
// validationsFailed.push('tests');
|
|
1648
|
-
// }
|
|
1649
|
-
// }
|
|
1650
|
-
|
|
1651
|
-
const totalErrors = errors.filter(e => e.severity === 'error').length;
|
|
1652
|
-
const totalWarnings = errors.filter(e => e.severity === 'warning').length;
|
|
1653
|
-
const isValid = totalErrors === 0;
|
|
1654
|
-
|
|
1655
|
-
return {
|
|
1656
|
-
valid: isValid,
|
|
1657
|
-
errors,
|
|
1658
|
-
summary: {
|
|
1659
|
-
totalErrors,
|
|
1660
|
-
totalWarnings,
|
|
1661
|
-
validationsPassed,
|
|
1662
|
-
validationsFailed,
|
|
1663
|
-
},
|
|
1664
|
-
};
|
|
1665
|
-
}
|
|
1666
|
-
|
|
1667
|
-
/**
|
|
1668
|
-
* Parse ESLint errors from JSON output
|
|
1669
|
-
*/
|
|
1670
|
-
static parseESLintErrors(eslintResults: any[]): Array<{
|
|
1671
|
-
type: 'eslint';
|
|
1672
|
-
severity: 'error' | 'warning';
|
|
1673
|
-
message: string;
|
|
1674
|
-
file?: string;
|
|
1675
|
-
line?: number;
|
|
1676
|
-
column?: number;
|
|
1677
|
-
code?: string;
|
|
1678
|
-
}> {
|
|
1679
|
-
const errors: Array<{
|
|
1680
|
-
type: 'eslint';
|
|
1681
|
-
severity: 'error' | 'warning';
|
|
1682
|
-
message: string;
|
|
1683
|
-
file?: string;
|
|
1684
|
-
line?: number;
|
|
1685
|
-
column?: number;
|
|
1686
|
-
code?: string;
|
|
1687
|
-
}> = [];
|
|
1688
|
-
|
|
1689
|
-
for (const result of eslintResults) {
|
|
1690
|
-
for (const message of result.messages || []) {
|
|
1691
|
-
if (message.message) {
|
|
1692
|
-
errors.push({
|
|
1693
|
-
type: 'eslint',
|
|
1694
|
-
severity: message.severity === 1 ? 'warning' : 'error',
|
|
1695
|
-
message: message.message,
|
|
1696
|
-
file: result.filePath || undefined,
|
|
1697
|
-
line: message.line || undefined,
|
|
1698
|
-
column: message.column || undefined,
|
|
1699
|
-
code: message.ruleId || undefined,
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
return errors;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
/**
|
|
1709
|
-
* Make HTTP request to server or external API
|
|
1710
|
-
*/
|
|
1711
|
-
static async makeHttpRequest({
|
|
1712
|
-
method,
|
|
1713
|
-
url,
|
|
1714
|
-
baseUrl,
|
|
1715
|
-
headers = {},
|
|
1716
|
-
body,
|
|
1717
|
-
timeout = 30000,
|
|
1718
|
-
}: {
|
|
1719
|
-
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
1720
|
-
url: string;
|
|
1721
|
-
baseUrl?: string;
|
|
1722
|
-
headers?: Record<string, string>;
|
|
1723
|
-
body?: any;
|
|
1724
|
-
timeout?: number;
|
|
1725
|
-
}) {
|
|
1726
|
-
try {
|
|
1727
|
-
const fullUrl = baseUrl ? `${baseUrl}${url}` : url;
|
|
1728
|
-
|
|
1729
|
-
const controller = new AbortController();
|
|
1730
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1731
|
-
|
|
1732
|
-
const requestOptions: RequestInit = {
|
|
1733
|
-
method,
|
|
1734
|
-
headers: {
|
|
1735
|
-
'Content-Type': 'application/json',
|
|
1736
|
-
...headers,
|
|
1737
|
-
},
|
|
1738
|
-
signal: controller.signal,
|
|
1739
|
-
};
|
|
1740
|
-
|
|
1741
|
-
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
1742
|
-
requestOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
const response = await fetch(fullUrl, requestOptions);
|
|
1746
|
-
clearTimeout(timeoutId);
|
|
1747
|
-
|
|
1748
|
-
let data: any;
|
|
1749
|
-
const contentType = response.headers.get('content-type');
|
|
1750
|
-
if (contentType?.includes('application/json')) {
|
|
1751
|
-
data = await response.json();
|
|
1752
|
-
} else {
|
|
1753
|
-
data = await response.text();
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
const responseHeaders: Record<string, string> = {};
|
|
1757
|
-
response.headers.forEach((value, key) => {
|
|
1758
|
-
responseHeaders[key] = value;
|
|
1759
|
-
});
|
|
1760
|
-
|
|
1761
|
-
return {
|
|
1762
|
-
success: response.ok,
|
|
1763
|
-
status: response.status,
|
|
1764
|
-
statusText: response.statusText,
|
|
1765
|
-
headers: responseHeaders,
|
|
1766
|
-
data,
|
|
1767
|
-
url: fullUrl,
|
|
1768
|
-
method,
|
|
1769
|
-
};
|
|
1770
|
-
} catch (error) {
|
|
1771
|
-
return {
|
|
1772
|
-
success: false,
|
|
1773
|
-
url: baseUrl ? `${baseUrl}${url}` : url,
|
|
1774
|
-
method,
|
|
1775
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
/**
|
|
1781
|
-
* Enhanced task management system for complex coding tasks
|
|
1782
|
-
*/
|
|
1783
|
-
static async manageTaskList(context: {
|
|
1784
|
-
action: 'create' | 'update' | 'list' | 'complete' | 'remove';
|
|
1785
|
-
tasks?: Array<{
|
|
1786
|
-
id: string;
|
|
1787
|
-
content?: string;
|
|
1788
|
-
status: 'pending' | 'in_progress' | 'completed' | 'blocked';
|
|
1789
|
-
priority: 'high' | 'medium' | 'low';
|
|
1790
|
-
dependencies?: string[];
|
|
1791
|
-
notes?: string;
|
|
1792
|
-
}>;
|
|
1793
|
-
taskId?: string;
|
|
1794
|
-
}) {
|
|
1795
|
-
// In-memory task storage with cleanup (could be enhanced with persistent storage)
|
|
1796
|
-
if (!AgentBuilderDefaults.taskStorage) {
|
|
1797
|
-
AgentBuilderDefaults.taskStorage = new Map();
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
// Cleanup old sessions to prevent memory leaks
|
|
1801
|
-
// Keep only the last 10 sessions
|
|
1802
|
-
const sessions = Array.from(AgentBuilderDefaults.taskStorage.keys());
|
|
1803
|
-
if (sessions.length > 10) {
|
|
1804
|
-
const sessionsToRemove = sessions.slice(0, sessions.length - 10);
|
|
1805
|
-
sessionsToRemove.forEach(session => AgentBuilderDefaults.taskStorage.delete(session));
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
const sessionId = 'current'; // Could be enhanced with proper session management
|
|
1809
|
-
const existingTasks = AgentBuilderDefaults.taskStorage.get(sessionId) || [];
|
|
1810
|
-
|
|
1811
|
-
try {
|
|
1812
|
-
switch (context.action) {
|
|
1813
|
-
case 'create':
|
|
1814
|
-
if (!context.tasks?.length) {
|
|
1815
|
-
return {
|
|
1816
|
-
success: false,
|
|
1817
|
-
tasks: existingTasks,
|
|
1818
|
-
message: 'No tasks provided for creation',
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
|
-
|
|
1822
|
-
const newTasks = context.tasks.map(task => ({
|
|
1823
|
-
...task,
|
|
1824
|
-
createdAt: new Date().toISOString(),
|
|
1825
|
-
updatedAt: new Date().toISOString(),
|
|
1826
|
-
}));
|
|
1827
|
-
|
|
1828
|
-
const allTasks = [...existingTasks, ...newTasks];
|
|
1829
|
-
AgentBuilderDefaults.taskStorage.set(sessionId, allTasks);
|
|
1830
|
-
|
|
1831
|
-
return {
|
|
1832
|
-
success: true,
|
|
1833
|
-
tasks: allTasks,
|
|
1834
|
-
message: `Created ${newTasks.length} new task(s)`,
|
|
1835
|
-
};
|
|
1836
|
-
|
|
1837
|
-
case 'update':
|
|
1838
|
-
if (!context.tasks?.length) {
|
|
1839
|
-
return {
|
|
1840
|
-
success: false,
|
|
1841
|
-
tasks: existingTasks,
|
|
1842
|
-
message: 'No tasks provided for update',
|
|
1843
|
-
};
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
const updatedTasks = existingTasks.map(existing => {
|
|
1847
|
-
const update = context.tasks!.find(t => t.id === existing.id);
|
|
1848
|
-
return update ? { ...existing, ...update, updatedAt: new Date().toISOString() } : existing;
|
|
1849
|
-
});
|
|
1850
|
-
|
|
1851
|
-
AgentBuilderDefaults.taskStorage.set(sessionId, updatedTasks);
|
|
1852
|
-
|
|
1853
|
-
return {
|
|
1854
|
-
success: true,
|
|
1855
|
-
tasks: updatedTasks,
|
|
1856
|
-
message: 'Tasks updated successfully',
|
|
1857
|
-
};
|
|
1858
|
-
|
|
1859
|
-
case 'complete':
|
|
1860
|
-
if (!context.taskId) {
|
|
1861
|
-
return {
|
|
1862
|
-
success: false,
|
|
1863
|
-
tasks: existingTasks,
|
|
1864
|
-
message: 'Task ID required for completion',
|
|
1865
|
-
};
|
|
1866
|
-
}
|
|
1867
|
-
|
|
1868
|
-
const completedTasks = existingTasks.map(task =>
|
|
1869
|
-
task.id === context.taskId
|
|
1870
|
-
? { ...task, status: 'completed' as const, updatedAt: new Date().toISOString() }
|
|
1871
|
-
: task,
|
|
1872
|
-
);
|
|
1873
|
-
|
|
1874
|
-
AgentBuilderDefaults.taskStorage.set(sessionId, completedTasks);
|
|
1875
|
-
|
|
1876
|
-
return {
|
|
1877
|
-
success: true,
|
|
1878
|
-
tasks: completedTasks,
|
|
1879
|
-
message: `Task ${context.taskId} marked as completed`,
|
|
1880
|
-
};
|
|
1881
|
-
|
|
1882
|
-
case 'remove':
|
|
1883
|
-
if (!context.taskId) {
|
|
1884
|
-
return {
|
|
1885
|
-
success: false,
|
|
1886
|
-
tasks: existingTasks,
|
|
1887
|
-
message: 'Task ID required for removal',
|
|
1888
|
-
};
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
const filteredTasks = existingTasks.filter(task => task.id !== context.taskId);
|
|
1892
|
-
AgentBuilderDefaults.taskStorage.set(sessionId, filteredTasks);
|
|
1893
|
-
|
|
1894
|
-
return {
|
|
1895
|
-
success: true,
|
|
1896
|
-
tasks: filteredTasks,
|
|
1897
|
-
message: `Task ${context.taskId} removed`,
|
|
1898
|
-
};
|
|
1899
|
-
|
|
1900
|
-
case 'list':
|
|
1901
|
-
default:
|
|
1902
|
-
return {
|
|
1903
|
-
success: true,
|
|
1904
|
-
tasks: existingTasks,
|
|
1905
|
-
message: `Found ${existingTasks.length} task(s)`,
|
|
1906
|
-
};
|
|
1907
|
-
}
|
|
1908
|
-
} catch (error) {
|
|
1909
|
-
return {
|
|
1910
|
-
success: false,
|
|
1911
|
-
tasks: existingTasks,
|
|
1912
|
-
message: `Task management error: ${error instanceof Error ? error.message : String(error)}`,
|
|
1913
|
-
};
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
/**
|
|
1918
|
-
* Analyze codebase structure and patterns
|
|
1919
|
-
*/
|
|
1920
|
-
static async analyzeCode(context: {
|
|
1921
|
-
action: 'definitions' | 'dependencies' | 'patterns' | 'structure';
|
|
1922
|
-
path: string;
|
|
1923
|
-
language?: string;
|
|
1924
|
-
depth?: number;
|
|
1925
|
-
includeTests?: boolean;
|
|
1926
|
-
}) {
|
|
1927
|
-
try {
|
|
1928
|
-
const { action, path, language, depth = 3 } = context;
|
|
1929
|
-
|
|
1930
|
-
// Use ripgrep for fast searching
|
|
1931
|
-
// const excludePatterns = includeTests ? [] : ['*test*', '*spec*', '__tests__'];
|
|
1932
|
-
|
|
1933
|
-
// Only allow a list of known extensions/language types to prevent shell injection
|
|
1934
|
-
const ALLOWED_LANGUAGES = [
|
|
1935
|
-
'js',
|
|
1936
|
-
'ts',
|
|
1937
|
-
'jsx',
|
|
1938
|
-
'tsx',
|
|
1939
|
-
'py',
|
|
1940
|
-
'java',
|
|
1941
|
-
'go',
|
|
1942
|
-
'cpp',
|
|
1943
|
-
'c',
|
|
1944
|
-
'cs',
|
|
1945
|
-
'rb',
|
|
1946
|
-
'php',
|
|
1947
|
-
'rs',
|
|
1948
|
-
'kt',
|
|
1949
|
-
'swift',
|
|
1950
|
-
'm',
|
|
1951
|
-
'scala',
|
|
1952
|
-
'sh',
|
|
1953
|
-
'json',
|
|
1954
|
-
'yaml',
|
|
1955
|
-
'yml',
|
|
1956
|
-
'toml',
|
|
1957
|
-
'ini',
|
|
1958
|
-
];
|
|
1959
|
-
let languagePattern = '*';
|
|
1960
|
-
if (language && ALLOWED_LANGUAGES.includes(language)) {
|
|
1961
|
-
languagePattern = `*.${language}`;
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
switch (action) {
|
|
1965
|
-
case 'definitions':
|
|
1966
|
-
// Search for function/class/interface definitions
|
|
1967
|
-
const definitionPatterns = [
|
|
1968
|
-
'function\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
|
|
1969
|
-
'class\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
|
|
1970
|
-
'interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
|
|
1971
|
-
'const\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*=',
|
|
1972
|
-
'export\\s+(function|class|interface|const)\\s+([a-zA-Z_][a-zA-Z0-9_]*)',
|
|
1973
|
-
];
|
|
1974
|
-
|
|
1975
|
-
const definitions: Array<{ name: string; type: string; file: string; line?: number; scope?: string }> = [];
|
|
1976
|
-
|
|
1977
|
-
for (const pattern of definitionPatterns) {
|
|
1978
|
-
try {
|
|
1979
|
-
const { stdout } = await execFile('rg', [
|
|
1980
|
-
'-n',
|
|
1981
|
-
pattern,
|
|
1982
|
-
path,
|
|
1983
|
-
'--type',
|
|
1984
|
-
languagePattern,
|
|
1985
|
-
'--max-depth',
|
|
1986
|
-
String(depth),
|
|
1987
|
-
]);
|
|
1988
|
-
const matches = stdout.split('\n').filter(line => line.trim());
|
|
1989
|
-
|
|
1990
|
-
matches.forEach(match => {
|
|
1991
|
-
const parts = match.split(':');
|
|
1992
|
-
if (parts.length >= 3) {
|
|
1993
|
-
const file = parts[0];
|
|
1994
|
-
const lineStr = parts[1];
|
|
1995
|
-
const line = parseInt(lineStr || '0');
|
|
1996
|
-
const content = parts.slice(2).join(':');
|
|
1997
|
-
const nameMatch = content.match(/([a-zA-Z_][a-zA-Z0-9_]*)/);
|
|
1998
|
-
|
|
1999
|
-
if (nameMatch && nameMatch[1]) {
|
|
2000
|
-
definitions.push({
|
|
2001
|
-
name: nameMatch[1],
|
|
2002
|
-
type: pattern.includes('function')
|
|
2003
|
-
? 'function'
|
|
2004
|
-
: pattern.includes('class')
|
|
2005
|
-
? 'class'
|
|
2006
|
-
: pattern.includes('interface')
|
|
2007
|
-
? 'interface'
|
|
2008
|
-
: 'variable',
|
|
2009
|
-
file: file || '',
|
|
2010
|
-
line,
|
|
2011
|
-
scope: 'top-level',
|
|
2012
|
-
});
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
});
|
|
2016
|
-
} catch {
|
|
2017
|
-
// Continue with other patterns if one fails
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
|
|
2021
|
-
return {
|
|
2022
|
-
success: true,
|
|
2023
|
-
analysis: { definitions },
|
|
2024
|
-
message: `Found ${definitions.length} code definitions`,
|
|
2025
|
-
};
|
|
2026
|
-
|
|
2027
|
-
case 'dependencies':
|
|
2028
|
-
// Search for import/require statements
|
|
2029
|
-
const depPatterns = [
|
|
2030
|
-
'import\\s+.*\\s+from\\s+[\'"]([^\'"]+)[\'"]',
|
|
2031
|
-
'require\\([\'"]([^\'"]+)[\'"]\\)',
|
|
2032
|
-
'#include\\s+[<"]([^>"]+)[>"]',
|
|
2033
|
-
];
|
|
2034
|
-
|
|
2035
|
-
const dependencies: Array<{
|
|
2036
|
-
name: string;
|
|
2037
|
-
type: 'import' | 'require' | 'include';
|
|
2038
|
-
source: string;
|
|
2039
|
-
target: string;
|
|
2040
|
-
}> = [];
|
|
2041
|
-
|
|
2042
|
-
for (const pattern of depPatterns) {
|
|
2043
|
-
try {
|
|
2044
|
-
const { stdout } = await execFile('rg', ['-n', pattern, path, '--type', languagePattern]);
|
|
2045
|
-
const matches = stdout.split('\n').filter(line => line.trim());
|
|
2046
|
-
|
|
2047
|
-
matches.forEach(match => {
|
|
2048
|
-
const parts = match.split(':');
|
|
2049
|
-
if (parts.length >= 3) {
|
|
2050
|
-
const file = parts[0];
|
|
2051
|
-
const content = parts.slice(2).join(':');
|
|
2052
|
-
const depMatch = content.match(new RegExp(pattern));
|
|
2053
|
-
|
|
2054
|
-
if (depMatch && depMatch[1]) {
|
|
2055
|
-
dependencies.push({
|
|
2056
|
-
name: depMatch[1],
|
|
2057
|
-
type: pattern.includes('import') ? 'import' : pattern.includes('require') ? 'require' : 'include',
|
|
2058
|
-
source: file || '',
|
|
2059
|
-
target: depMatch[1],
|
|
2060
|
-
});
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
});
|
|
2064
|
-
} catch {
|
|
2065
|
-
// Continue with other patterns
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
|
|
2069
|
-
return {
|
|
2070
|
-
success: true,
|
|
2071
|
-
analysis: { dependencies },
|
|
2072
|
-
message: `Found ${dependencies.length} dependencies`,
|
|
2073
|
-
};
|
|
2074
|
-
|
|
2075
|
-
case 'structure':
|
|
2076
|
-
// Use execFile for find commands to avoid shell injection
|
|
2077
|
-
const { stdout: lsOutput } = await execFile('find', [path, '-type', 'f', '-name', languagePattern]);
|
|
2078
|
-
const allFiles = lsOutput.split('\n').filter(line => line.trim());
|
|
2079
|
-
const files = allFiles.slice(0, 1000); // Limit to 1000 files like head -1000
|
|
2080
|
-
|
|
2081
|
-
const { stdout: dirOutput } = await execFile('find', [path, '-type', 'd']);
|
|
2082
|
-
const directories = dirOutput.split('\n').filter(line => line.trim()).length;
|
|
2083
|
-
|
|
2084
|
-
// Count languages by file extension
|
|
2085
|
-
const languages: Record<string, number> = {};
|
|
2086
|
-
files.forEach(file => {
|
|
2087
|
-
const ext = file.split('.').pop();
|
|
2088
|
-
if (ext) {
|
|
2089
|
-
languages[ext] = (languages[ext] || 0) + 1;
|
|
2090
|
-
}
|
|
2091
|
-
});
|
|
2092
|
-
|
|
2093
|
-
const complexity = files.length > 1000 ? 'high' : files.length > 100 ? 'medium' : 'low';
|
|
2094
|
-
|
|
2095
|
-
return {
|
|
2096
|
-
success: true,
|
|
2097
|
-
analysis: {
|
|
2098
|
-
structure: {
|
|
2099
|
-
directories,
|
|
2100
|
-
files: files.length,
|
|
2101
|
-
languages,
|
|
2102
|
-
complexity,
|
|
2103
|
-
},
|
|
2104
|
-
},
|
|
2105
|
-
message: `Analyzed project structure: ${files.length} files in ${directories} directories`,
|
|
2106
|
-
};
|
|
2107
|
-
|
|
2108
|
-
default:
|
|
2109
|
-
return {
|
|
2110
|
-
success: false,
|
|
2111
|
-
analysis: {},
|
|
2112
|
-
message: `Unknown analysis action: ${action}`,
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
} catch (error) {
|
|
2116
|
-
return {
|
|
2117
|
-
success: false,
|
|
2118
|
-
analysis: {},
|
|
2119
|
-
message: `Code analysis error: ${error instanceof Error ? error.message : String(error)}`,
|
|
2120
|
-
};
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
/**
|
|
2125
|
-
* Perform multiple edits across files atomically
|
|
2126
|
-
*/
|
|
2127
|
-
static async performMultiEdit(context: {
|
|
2128
|
-
operations: Array<{
|
|
2129
|
-
filePath: string;
|
|
2130
|
-
edits: Array<{
|
|
2131
|
-
oldString: string;
|
|
2132
|
-
newString: string;
|
|
2133
|
-
replaceAll?: boolean;
|
|
2134
|
-
}>;
|
|
2135
|
-
}>;
|
|
2136
|
-
createBackup?: boolean;
|
|
2137
|
-
projectPath?: string;
|
|
2138
|
-
}) {
|
|
2139
|
-
const { operations, createBackup = false, projectPath = process.cwd() } = context;
|
|
2140
|
-
const results: Array<{
|
|
2141
|
-
filePath: string;
|
|
2142
|
-
editsApplied: number;
|
|
2143
|
-
errors: string[];
|
|
2144
|
-
backup?: string;
|
|
2145
|
-
}> = [];
|
|
2146
|
-
|
|
2147
|
-
try {
|
|
2148
|
-
for (const operation of operations) {
|
|
2149
|
-
const filePath = isAbsolute(operation.filePath) ? operation.filePath : join(projectPath, operation.filePath);
|
|
2150
|
-
let editsApplied = 0;
|
|
2151
|
-
const errors: string[] = [];
|
|
2152
|
-
let backup: string | undefined;
|
|
2153
|
-
|
|
2154
|
-
try {
|
|
2155
|
-
// Create backup if requested
|
|
2156
|
-
if (createBackup) {
|
|
2157
|
-
const backupPath = `${filePath}.backup.${Date.now()}`;
|
|
2158
|
-
const originalContent = await readFile(filePath, 'utf-8');
|
|
2159
|
-
await writeFile(backupPath, originalContent, 'utf-8');
|
|
2160
|
-
backup = backupPath;
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
// Read current file content
|
|
2164
|
-
let content = await readFile(filePath, 'utf-8');
|
|
2165
|
-
|
|
2166
|
-
// Apply each edit
|
|
2167
|
-
for (const edit of operation.edits) {
|
|
2168
|
-
const { oldString, newString, replaceAll = false } = edit;
|
|
2169
|
-
|
|
2170
|
-
if (replaceAll) {
|
|
2171
|
-
const regex = new RegExp(oldString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
|
|
2172
|
-
const matches = content.match(regex);
|
|
2173
|
-
if (matches) {
|
|
2174
|
-
content = content.replace(regex, newString);
|
|
2175
|
-
editsApplied += matches.length;
|
|
2176
|
-
}
|
|
2177
|
-
} else {
|
|
2178
|
-
if (content.includes(oldString)) {
|
|
2179
|
-
content = content.replace(oldString, newString);
|
|
2180
|
-
editsApplied++;
|
|
2181
|
-
} else {
|
|
2182
|
-
errors.push(`String not found: "${oldString.substring(0, 50)}${oldString.length > 50 ? '...' : ''}"`);
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
// Write updated content back
|
|
2188
|
-
await writeFile(filePath, content, 'utf-8');
|
|
2189
|
-
} catch (error) {
|
|
2190
|
-
errors.push(`File operation error: ${error instanceof Error ? error.message : String(error)}`);
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
results.push({
|
|
2194
|
-
filePath: operation.filePath,
|
|
2195
|
-
editsApplied,
|
|
2196
|
-
errors,
|
|
2197
|
-
backup,
|
|
2198
|
-
});
|
|
2199
|
-
}
|
|
2200
|
-
|
|
2201
|
-
const totalEdits = results.reduce((sum, r) => sum + r.editsApplied, 0);
|
|
2202
|
-
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
|
|
2203
|
-
|
|
2204
|
-
return {
|
|
2205
|
-
success: totalErrors === 0,
|
|
2206
|
-
results,
|
|
2207
|
-
message: `Applied ${totalEdits} edits across ${operations.length} files${totalErrors > 0 ? ` with ${totalErrors} errors` : ''}`,
|
|
2208
|
-
};
|
|
2209
|
-
} catch (error) {
|
|
2210
|
-
return {
|
|
2211
|
-
success: false,
|
|
2212
|
-
results,
|
|
2213
|
-
message: `Multi-edit operation failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
2214
|
-
};
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
/**
|
|
2219
|
-
* Replace specific line ranges in a file with new content
|
|
2220
|
-
*/
|
|
2221
|
-
static async replaceLines(context: {
|
|
2222
|
-
filePath: string;
|
|
2223
|
-
startLine: number;
|
|
2224
|
-
endLine: number;
|
|
2225
|
-
newContent: string;
|
|
2226
|
-
createBackup?: boolean;
|
|
2227
|
-
projectPath?: string;
|
|
2228
|
-
}) {
|
|
2229
|
-
const { filePath, startLine, endLine, newContent, createBackup = false, projectPath = process.cwd() } = context;
|
|
2230
|
-
|
|
2231
|
-
try {
|
|
2232
|
-
const fullPath = isAbsolute(filePath) ? filePath : join(projectPath, filePath);
|
|
2233
|
-
|
|
2234
|
-
// Read current file content
|
|
2235
|
-
const content = await readFile(fullPath, 'utf-8');
|
|
2236
|
-
const lines = content.split('\n');
|
|
2237
|
-
|
|
2238
|
-
// Validate line numbers
|
|
2239
|
-
if (startLine < 1 || endLine < 1 || startLine > lines.length || endLine > lines.length) {
|
|
2240
|
-
return {
|
|
2241
|
-
success: false,
|
|
2242
|
-
message: `Invalid line range: ${startLine}-${endLine}. File has ${lines.length} lines.`,
|
|
2243
|
-
error: 'Invalid line range',
|
|
2244
|
-
};
|
|
2245
|
-
}
|
|
2246
|
-
|
|
2247
|
-
if (startLine > endLine) {
|
|
2248
|
-
return {
|
|
2249
|
-
success: false,
|
|
2250
|
-
message: `Start line (${startLine}) cannot be greater than end line (${endLine}).`,
|
|
2251
|
-
error: 'Invalid line range',
|
|
2252
|
-
};
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
// Create backup if requested
|
|
2256
|
-
let backup: string | undefined;
|
|
2257
|
-
if (createBackup) {
|
|
2258
|
-
const backupPath = `${fullPath}.backup.${Date.now()}`;
|
|
2259
|
-
await writeFile(backupPath, content, 'utf-8');
|
|
2260
|
-
backup = backupPath;
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
// Replace the specified line range
|
|
2264
|
-
const beforeLines = lines.slice(0, startLine - 1);
|
|
2265
|
-
const afterLines = lines.slice(endLine);
|
|
2266
|
-
const newLines = newContent ? newContent.split('\n') : [];
|
|
2267
|
-
|
|
2268
|
-
const updatedLines = [...beforeLines, ...newLines, ...afterLines];
|
|
2269
|
-
const updatedContent = updatedLines.join('\n');
|
|
2270
|
-
|
|
2271
|
-
// Write updated content back
|
|
2272
|
-
await writeFile(fullPath, updatedContent, 'utf-8');
|
|
2273
|
-
|
|
2274
|
-
const linesReplaced = endLine - startLine + 1;
|
|
2275
|
-
|
|
2276
|
-
return {
|
|
2277
|
-
success: true,
|
|
2278
|
-
message: `Successfully replaced ${linesReplaced} lines (${startLine}-${endLine}) in ${filePath}`,
|
|
2279
|
-
linesReplaced,
|
|
2280
|
-
backup,
|
|
2281
|
-
};
|
|
2282
|
-
} catch (error) {
|
|
2283
|
-
return {
|
|
2284
|
-
success: false,
|
|
2285
|
-
message: `Failed to replace lines: ${error instanceof Error ? error.message : String(error)}`,
|
|
2286
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2287
|
-
};
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
/**
|
|
2292
|
-
* Ask user for clarification
|
|
2293
|
-
*/
|
|
2294
|
-
static async askClarification(context: {
|
|
2295
|
-
question: string;
|
|
2296
|
-
options?: Array<{ id: string; description: string; implications?: string }>;
|
|
2297
|
-
context?: string;
|
|
2298
|
-
urgency?: 'low' | 'medium' | 'high';
|
|
2299
|
-
}) {
|
|
2300
|
-
const questionId = `q_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2301
|
-
|
|
2302
|
-
// Store question for potential follow-up (in real implementation, this might be stored in session state)
|
|
2303
|
-
if (!AgentBuilderDefaults.pendingQuestions) {
|
|
2304
|
-
AgentBuilderDefaults.pendingQuestions = new Map();
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
AgentBuilderDefaults.pendingQuestions.set(questionId, {
|
|
2308
|
-
...context,
|
|
2309
|
-
timestamp: new Date().toISOString(),
|
|
2310
|
-
});
|
|
2311
|
-
|
|
2312
|
-
return {
|
|
2313
|
-
questionId,
|
|
2314
|
-
question: context.question,
|
|
2315
|
-
options: context.options?.map(opt => ({ id: opt.id, description: opt.description })),
|
|
2316
|
-
awaitingResponse: true,
|
|
2317
|
-
};
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
/**
|
|
2321
|
-
* Signal task completion
|
|
2322
|
-
*/
|
|
2323
|
-
static async signalCompletion(context: {
|
|
2324
|
-
summary: string;
|
|
2325
|
-
changes: Array<{
|
|
2326
|
-
type: 'file_created' | 'file_modified' | 'file_deleted' | 'command_executed' | 'dependency_added';
|
|
2327
|
-
description: string;
|
|
2328
|
-
path?: string;
|
|
2329
|
-
}>;
|
|
2330
|
-
validation: {
|
|
2331
|
-
testsRun?: boolean;
|
|
2332
|
-
buildsSuccessfully?: boolean;
|
|
2333
|
-
manualTestingRequired?: boolean;
|
|
2334
|
-
};
|
|
2335
|
-
nextSteps?: string[];
|
|
2336
|
-
}) {
|
|
2337
|
-
const completionId = `completion_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
2338
|
-
|
|
2339
|
-
// Calculate confidence based on validation status
|
|
2340
|
-
let confidence = 70; // Base confidence
|
|
2341
|
-
if (context.validation.testsRun) confidence += 15;
|
|
2342
|
-
if (context.validation.buildsSuccessfully) confidence += 15;
|
|
2343
|
-
if (context.validation.manualTestingRequired) confidence -= 10;
|
|
2344
|
-
|
|
2345
|
-
// Determine status
|
|
2346
|
-
let status: 'completed' | 'needs_review' | 'needs_testing';
|
|
2347
|
-
if (context.validation.testsRun && context.validation.buildsSuccessfully) {
|
|
2348
|
-
status = 'completed';
|
|
2349
|
-
} else if (context.validation.manualTestingRequired) {
|
|
2350
|
-
status = 'needs_testing';
|
|
2351
|
-
} else {
|
|
2352
|
-
status = 'needs_review';
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
return {
|
|
2356
|
-
completionId,
|
|
2357
|
-
status,
|
|
2358
|
-
summary: context.summary,
|
|
2359
|
-
confidence: Math.min(100, Math.max(0, confidence)),
|
|
2360
|
-
};
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
/**
|
|
2364
|
-
* Perform intelligent search with context
|
|
2365
|
-
*/
|
|
2366
|
-
static async performSmartSearch(
|
|
2367
|
-
context: {
|
|
2368
|
-
query: string;
|
|
2369
|
-
type?: 'text' | 'regex' | 'fuzzy' | 'semantic';
|
|
2370
|
-
scope?: {
|
|
2371
|
-
paths?: string[];
|
|
2372
|
-
fileTypes?: string[];
|
|
2373
|
-
excludePaths?: string[];
|
|
2374
|
-
maxResults?: number;
|
|
2375
|
-
};
|
|
2376
|
-
context?: {
|
|
2377
|
-
beforeLines?: number;
|
|
2378
|
-
afterLines?: number;
|
|
2379
|
-
includeDefinitions?: boolean;
|
|
2380
|
-
};
|
|
2381
|
-
},
|
|
2382
|
-
projectPath: string,
|
|
2383
|
-
) {
|
|
2384
|
-
try {
|
|
2385
|
-
const { query, type = 'text', scope = {}, context: searchContext = {} } = context;
|
|
2386
|
-
|
|
2387
|
-
const { paths = ['.'], fileTypes = [], excludePaths = [], maxResults = 50 } = scope;
|
|
2388
|
-
|
|
2389
|
-
const { beforeLines = 2, afterLines = 2 } = searchContext;
|
|
2390
|
-
|
|
2391
|
-
// Build command and arguments array safely
|
|
2392
|
-
const rgArgs: string[] = [];
|
|
2393
|
-
|
|
2394
|
-
// Add context lines
|
|
2395
|
-
if (beforeLines > 0) {
|
|
2396
|
-
rgArgs.push('-B', beforeLines.toString());
|
|
2397
|
-
}
|
|
2398
|
-
if (afterLines > 0) {
|
|
2399
|
-
rgArgs.push('-A', afterLines.toString());
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
// Add line numbers
|
|
2403
|
-
rgArgs.push('-n');
|
|
2404
|
-
|
|
2405
|
-
// Handle search type
|
|
2406
|
-
if (type === 'regex') {
|
|
2407
|
-
rgArgs.push('-e');
|
|
2408
|
-
} else if (type === 'fuzzy') {
|
|
2409
|
-
rgArgs.push('--fixed-strings');
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
// Add file type filters
|
|
2413
|
-
if (fileTypes.length > 0) {
|
|
2414
|
-
fileTypes.forEach(ft => {
|
|
2415
|
-
rgArgs.push('--type-add', `custom:*.${ft}`, '-t', 'custom');
|
|
2416
|
-
});
|
|
2417
|
-
}
|
|
2418
|
-
|
|
2419
|
-
// Add exclude patterns
|
|
2420
|
-
excludePaths.forEach(path => {
|
|
2421
|
-
rgArgs.push('--glob', `!${path}`);
|
|
2422
|
-
});
|
|
2423
|
-
|
|
2424
|
-
// Add max count
|
|
2425
|
-
rgArgs.push('-m', maxResults.toString());
|
|
2426
|
-
|
|
2427
|
-
// Add the search query and paths
|
|
2428
|
-
rgArgs.push(query);
|
|
2429
|
-
rgArgs.push(...paths);
|
|
2430
|
-
|
|
2431
|
-
// Execute safely using execFile
|
|
2432
|
-
const { stdout } = await execFile('rg', rgArgs, {
|
|
2433
|
-
cwd: projectPath,
|
|
2434
|
-
});
|
|
2435
|
-
const lines = stdout.split('\n').filter(line => line.trim());
|
|
2436
|
-
|
|
2437
|
-
const matches: Array<{
|
|
2438
|
-
file: string;
|
|
2439
|
-
line: number;
|
|
2440
|
-
column?: number;
|
|
2441
|
-
match: string;
|
|
2442
|
-
context: { before: string[]; after: string[] };
|
|
2443
|
-
relevance?: number;
|
|
2444
|
-
}> = [];
|
|
2445
|
-
|
|
2446
|
-
let currentMatch: any = null;
|
|
2447
|
-
|
|
2448
|
-
lines.forEach(line => {
|
|
2449
|
-
if (line.includes(':') && !line.startsWith('-')) {
|
|
2450
|
-
// This is a match line
|
|
2451
|
-
const parts = line.split(':');
|
|
2452
|
-
if (parts.length >= 3) {
|
|
2453
|
-
// Save previous match if exists
|
|
2454
|
-
if (currentMatch) {
|
|
2455
|
-
matches.push(currentMatch);
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
currentMatch = {
|
|
2459
|
-
file: parts[0] || '',
|
|
2460
|
-
line: parseInt(parts[1] || '0'),
|
|
2461
|
-
match: parts.slice(2).join(':'),
|
|
2462
|
-
context: { before: [], after: [] },
|
|
2463
|
-
relevance: type === 'fuzzy' ? Math.random() * 100 : undefined,
|
|
2464
|
-
};
|
|
2465
|
-
}
|
|
2466
|
-
} else if (line.startsWith('-') && currentMatch) {
|
|
2467
|
-
// This is a context line
|
|
2468
|
-
const contextLine = line.substring(1);
|
|
2469
|
-
if (currentMatch.context.before.length < beforeLines) {
|
|
2470
|
-
currentMatch.context.before.push(contextLine);
|
|
2471
|
-
} else {
|
|
2472
|
-
currentMatch.context.after.push(contextLine);
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
});
|
|
2476
|
-
|
|
2477
|
-
// Add the last match
|
|
2478
|
-
if (currentMatch) {
|
|
2479
|
-
matches.push(currentMatch);
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
// Count files searched (approximate)
|
|
2483
|
-
const filesSearched = new Set(matches.map(m => m.file)).size;
|
|
2484
|
-
|
|
2485
|
-
return {
|
|
2486
|
-
success: true,
|
|
2487
|
-
matches: matches.slice(0, maxResults),
|
|
2488
|
-
summary: {
|
|
2489
|
-
totalMatches: matches.length,
|
|
2490
|
-
filesSearched,
|
|
2491
|
-
patterns: [query],
|
|
2492
|
-
},
|
|
2493
|
-
};
|
|
2494
|
-
} catch {
|
|
2495
|
-
return {
|
|
2496
|
-
success: false,
|
|
2497
|
-
matches: [],
|
|
2498
|
-
summary: {
|
|
2499
|
-
totalMatches: 0,
|
|
2500
|
-
filesSearched: 0,
|
|
2501
|
-
patterns: [context.query],
|
|
2502
|
-
},
|
|
2503
|
-
};
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
|
|
2507
|
-
// Static storage properties
|
|
2508
|
-
private static taskStorage: Map<string, any[]>;
|
|
2509
|
-
private static pendingQuestions: Map<string, any>;
|
|
2510
|
-
|
|
2511
|
-
/**
|
|
2512
|
-
* Read file contents with optional line range
|
|
2513
|
-
*/
|
|
2514
|
-
static async readFile(context: {
|
|
2515
|
-
filePath: string;
|
|
2516
|
-
startLine?: number;
|
|
2517
|
-
endLine?: number;
|
|
2518
|
-
encoding?: string;
|
|
2519
|
-
projectPath?: string;
|
|
2520
|
-
}) {
|
|
2521
|
-
try {
|
|
2522
|
-
const { filePath, startLine, endLine, encoding = 'utf-8', projectPath } = context;
|
|
2523
|
-
|
|
2524
|
-
// Resolve path relative to project directory if it's not absolute
|
|
2525
|
-
const resolvedPath = isAbsolute(filePath) ? filePath : resolve(projectPath || process.cwd(), filePath);
|
|
2526
|
-
|
|
2527
|
-
const stats = await stat(resolvedPath);
|
|
2528
|
-
const content = await readFile(resolvedPath, { encoding: encoding as BufferEncoding });
|
|
2529
|
-
const lines = content.split('\n');
|
|
2530
|
-
|
|
2531
|
-
let resultContent = content;
|
|
2532
|
-
let resultLines = lines;
|
|
2533
|
-
|
|
2534
|
-
if (startLine !== undefined || endLine !== undefined) {
|
|
2535
|
-
const start = Math.max(0, (startLine || 1) - 1);
|
|
2536
|
-
const end = endLine !== undefined ? Math.min(lines.length, endLine) : lines.length;
|
|
2537
|
-
resultLines = lines.slice(start, end);
|
|
2538
|
-
resultContent = resultLines.join('\n');
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
return {
|
|
2542
|
-
success: true,
|
|
2543
|
-
content: resultContent,
|
|
2544
|
-
lines: resultLines,
|
|
2545
|
-
metadata: {
|
|
2546
|
-
size: stats.size,
|
|
2547
|
-
totalLines: lines.length,
|
|
2548
|
-
encoding,
|
|
2549
|
-
lastModified: stats.mtime.toISOString(),
|
|
2550
|
-
},
|
|
2551
|
-
};
|
|
2552
|
-
} catch (error) {
|
|
2553
|
-
return {
|
|
2554
|
-
success: false,
|
|
2555
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2556
|
-
};
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
/**
|
|
2561
|
-
* Write content to file with directory creation and backup options
|
|
2562
|
-
*/
|
|
2563
|
-
static async writeFile(context: {
|
|
2564
|
-
filePath: string;
|
|
2565
|
-
content: string;
|
|
2566
|
-
createDirs?: boolean;
|
|
2567
|
-
encoding?: string;
|
|
2568
|
-
projectPath?: string;
|
|
2569
|
-
}) {
|
|
2570
|
-
try {
|
|
2571
|
-
const { filePath, content, createDirs = true, encoding = 'utf-8', projectPath } = context;
|
|
2572
|
-
|
|
2573
|
-
// Resolve path relative to project directory if it's not absolute
|
|
2574
|
-
const resolvedPath = isAbsolute(filePath) ? filePath : resolve(projectPath || process.cwd(), filePath);
|
|
2575
|
-
const dir = dirname(resolvedPath);
|
|
2576
|
-
|
|
2577
|
-
// Create directories if needed
|
|
2578
|
-
if (createDirs) {
|
|
2579
|
-
await mkdir(dir, { recursive: true });
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
|
-
// Write the file
|
|
2583
|
-
await writeFile(resolvedPath, content, { encoding: encoding as BufferEncoding });
|
|
2584
|
-
|
|
2585
|
-
return {
|
|
2586
|
-
success: true,
|
|
2587
|
-
filePath: resolvedPath,
|
|
2588
|
-
bytesWritten: Buffer.byteLength(content, encoding as BufferEncoding),
|
|
2589
|
-
message: `Successfully wrote ${Buffer.byteLength(content, encoding as BufferEncoding)} bytes to ${filePath}`,
|
|
2590
|
-
};
|
|
2591
|
-
} catch (error) {
|
|
2592
|
-
return {
|
|
2593
|
-
success: false,
|
|
2594
|
-
filePath: context.filePath,
|
|
2595
|
-
message: `Failed to write file: ${error instanceof Error ? error.message : String(error)}`,
|
|
2596
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2597
|
-
};
|
|
2598
|
-
}
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
/**
|
|
2602
|
-
* List directory contents with filtering and metadata
|
|
2603
|
-
*/
|
|
2604
|
-
static async listDirectory(context: {
|
|
2605
|
-
path: string;
|
|
2606
|
-
recursive?: boolean;
|
|
2607
|
-
includeHidden?: boolean;
|
|
2608
|
-
pattern?: string;
|
|
2609
|
-
maxDepth?: number;
|
|
2610
|
-
includeMetadata?: boolean;
|
|
2611
|
-
projectPath?: string;
|
|
2612
|
-
}) {
|
|
2613
|
-
try {
|
|
2614
|
-
const {
|
|
2615
|
-
path,
|
|
2616
|
-
recursive = false,
|
|
2617
|
-
includeHidden = false,
|
|
2618
|
-
pattern,
|
|
2619
|
-
maxDepth = 10,
|
|
2620
|
-
includeMetadata = true,
|
|
2621
|
-
projectPath,
|
|
2622
|
-
} = context;
|
|
2623
|
-
|
|
2624
|
-
const gitignorePath = join(projectPath || process.cwd(), '.gitignore');
|
|
2625
|
-
let gitignoreFilter: ignore.Ignore | undefined;
|
|
2626
|
-
|
|
2627
|
-
try {
|
|
2628
|
-
const gitignoreContent = await readFile(gitignorePath, 'utf-8');
|
|
2629
|
-
gitignoreFilter = ignore().add(gitignoreContent);
|
|
2630
|
-
} catch (err: any) {
|
|
2631
|
-
if (err.code !== 'ENOENT') {
|
|
2632
|
-
console.error(`Error reading .gitignore file:`, err);
|
|
2633
|
-
}
|
|
2634
|
-
// If .gitignore doesn't exist, gitignoreFilter remains undefined, meaning no files are ignored by gitignore.
|
|
2635
|
-
}
|
|
2636
|
-
|
|
2637
|
-
// Resolve path relative to project directory if it's not absolute
|
|
2638
|
-
const resolvedPath = isAbsolute(path) ? path : resolve(projectPath || process.cwd(), path);
|
|
2639
|
-
|
|
2640
|
-
const items: Array<{
|
|
2641
|
-
name: string;
|
|
2642
|
-
path: string;
|
|
2643
|
-
type: 'file' | 'directory' | 'symlink';
|
|
2644
|
-
size?: number;
|
|
2645
|
-
lastModified?: string;
|
|
2646
|
-
permissions?: string;
|
|
2647
|
-
}> = [];
|
|
2648
|
-
|
|
2649
|
-
async function processDirectory(dirPath: string, currentDepth: number = 0) {
|
|
2650
|
-
const relativeToProject = relative(projectPath || process.cwd(), dirPath);
|
|
2651
|
-
if (gitignoreFilter?.ignores(relativeToProject)) return;
|
|
2652
|
-
if (currentDepth > maxDepth) return;
|
|
2653
|
-
|
|
2654
|
-
const entries = await readdir(dirPath);
|
|
2655
|
-
|
|
2656
|
-
for (const entry of entries) {
|
|
2657
|
-
const entryPath = join(dirPath, entry);
|
|
2658
|
-
const relativeEntryPath = relative(projectPath || process.cwd(), entryPath);
|
|
2659
|
-
if (gitignoreFilter?.ignores(relativeEntryPath)) continue;
|
|
2660
|
-
if (!includeHidden && entry.startsWith('.')) continue;
|
|
2661
|
-
|
|
2662
|
-
const fullPath = entryPath;
|
|
2663
|
-
const relativePath = relative(resolvedPath, fullPath);
|
|
2664
|
-
|
|
2665
|
-
if (pattern) {
|
|
2666
|
-
// Simple pattern matching
|
|
2667
|
-
const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
2668
|
-
if (!new RegExp(regexPattern).test(entry)) continue;
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
let stats;
|
|
2672
|
-
let type: 'file' | 'directory' | 'symlink';
|
|
2673
|
-
|
|
2674
|
-
try {
|
|
2675
|
-
stats = await stat(fullPath);
|
|
2676
|
-
if (stats.isDirectory()) {
|
|
2677
|
-
type = 'directory';
|
|
2678
|
-
} else if (stats.isSymbolicLink()) {
|
|
2679
|
-
type = 'symlink';
|
|
2680
|
-
} else {
|
|
2681
|
-
type = 'file';
|
|
2682
|
-
}
|
|
2683
|
-
} catch {
|
|
2684
|
-
continue; // Skip entries we can't stat
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
const item: any = {
|
|
2688
|
-
name: entry,
|
|
2689
|
-
path: relativePath || entry,
|
|
2690
|
-
type,
|
|
2691
|
-
};
|
|
2692
|
-
|
|
2693
|
-
if (includeMetadata) {
|
|
2694
|
-
item.size = stats.size;
|
|
2695
|
-
item.lastModified = stats.mtime.toISOString();
|
|
2696
|
-
item.permissions = `0${(stats.mode & parseInt('777', 8)).toString(8)}`;
|
|
2697
|
-
}
|
|
2698
|
-
|
|
2699
|
-
items.push(item);
|
|
2700
|
-
|
|
2701
|
-
// Recurse into directories if requested
|
|
2702
|
-
if (recursive && type === 'directory') {
|
|
2703
|
-
await processDirectory(fullPath, currentDepth + 1);
|
|
2704
|
-
}
|
|
2705
|
-
}
|
|
2706
|
-
}
|
|
2707
|
-
|
|
2708
|
-
await processDirectory(resolvedPath);
|
|
2709
|
-
|
|
2710
|
-
return {
|
|
2711
|
-
success: true,
|
|
2712
|
-
items,
|
|
2713
|
-
totalItems: items.length,
|
|
2714
|
-
path: resolvedPath,
|
|
2715
|
-
message: `Listed ${items.length} items in ${resolvedPath}`,
|
|
2716
|
-
};
|
|
2717
|
-
} catch (error) {
|
|
2718
|
-
return {
|
|
2719
|
-
success: false,
|
|
2720
|
-
items: [],
|
|
2721
|
-
totalItems: 0,
|
|
2722
|
-
path: context.path,
|
|
2723
|
-
message: `Failed to list directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
2724
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2725
|
-
};
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
|
|
2729
|
-
/**
|
|
2730
|
-
* Execute shell commands with proper error handling
|
|
2731
|
-
*/
|
|
2732
|
-
static async executeCommand(context: {
|
|
2733
|
-
command: string;
|
|
2734
|
-
workingDirectory?: string;
|
|
2735
|
-
timeout?: number;
|
|
2736
|
-
captureOutput?: boolean;
|
|
2737
|
-
shell?: string;
|
|
2738
|
-
env?: Record<string, string>;
|
|
2739
|
-
}) {
|
|
2740
|
-
const startTime = Date.now();
|
|
2741
|
-
try {
|
|
2742
|
-
const { command, workingDirectory, timeout = 30000, captureOutput = true, shell, env } = context;
|
|
2743
|
-
|
|
2744
|
-
const execOptions: any = {
|
|
2745
|
-
timeout,
|
|
2746
|
-
env: { ...process.env, ...env },
|
|
2747
|
-
};
|
|
2748
|
-
|
|
2749
|
-
if (workingDirectory) {
|
|
2750
|
-
execOptions.cwd = workingDirectory;
|
|
2751
|
-
}
|
|
2752
|
-
|
|
2753
|
-
if (shell) {
|
|
2754
|
-
execOptions.shell = shell;
|
|
2755
|
-
}
|
|
2756
|
-
|
|
2757
|
-
const { stdout, stderr } = await exec(command, execOptions);
|
|
2758
|
-
const executionTime = Date.now() - startTime;
|
|
2759
|
-
|
|
2760
|
-
return {
|
|
2761
|
-
success: true,
|
|
2762
|
-
exitCode: 0,
|
|
2763
|
-
stdout: captureOutput ? String(stdout) : undefined,
|
|
2764
|
-
stderr: captureOutput ? String(stderr) : undefined,
|
|
2765
|
-
command,
|
|
2766
|
-
workingDirectory,
|
|
2767
|
-
executionTime,
|
|
2768
|
-
};
|
|
2769
|
-
} catch (error: any) {
|
|
2770
|
-
const executionTime = Date.now() - startTime;
|
|
2771
|
-
|
|
2772
|
-
return {
|
|
2773
|
-
success: false,
|
|
2774
|
-
exitCode: error.code || 1,
|
|
2775
|
-
stdout: String(error.stdout || ''),
|
|
2776
|
-
stderr: String(error.stderr || ''),
|
|
2777
|
-
command: context.command,
|
|
2778
|
-
workingDirectory: context.workingDirectory,
|
|
2779
|
-
executionTime,
|
|
2780
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2781
|
-
};
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2784
|
-
|
|
2785
|
-
/**
|
|
2786
|
-
* Web search using a simple search approach
|
|
2787
|
-
*/
|
|
2788
|
-
static async webSearch(context: {
|
|
2789
|
-
query: string;
|
|
2790
|
-
maxResults?: number;
|
|
2791
|
-
region?: string;
|
|
2792
|
-
language?: string;
|
|
2793
|
-
includeImages?: boolean;
|
|
2794
|
-
dateRange?: 'day' | 'week' | 'month' | 'year' | 'all';
|
|
2795
|
-
}) {
|
|
2796
|
-
try {
|
|
2797
|
-
const {
|
|
2798
|
-
query,
|
|
2799
|
-
maxResults = 10,
|
|
2800
|
-
// region = 'us',
|
|
2801
|
-
// language = 'en',
|
|
2802
|
-
// includeImages = false,
|
|
2803
|
-
// dateRange = 'all',
|
|
2804
|
-
} = context;
|
|
2805
|
-
|
|
2806
|
-
const startTime = Date.now();
|
|
2807
|
-
|
|
2808
|
-
// For now, implement a basic search using DuckDuckGo's instant answer API
|
|
2809
|
-
// In a real implementation, you'd want to use a proper search API
|
|
2810
|
-
const searchUrl = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_redirect=1&skip_disambig=1`;
|
|
2811
|
-
|
|
2812
|
-
const response = await fetch(searchUrl);
|
|
2813
|
-
const data: any = await response.json();
|
|
2814
|
-
|
|
2815
|
-
const results: Array<{
|
|
2816
|
-
title: string;
|
|
2817
|
-
url: string;
|
|
2818
|
-
snippet: string;
|
|
2819
|
-
domain: string;
|
|
2820
|
-
publishDate?: string;
|
|
2821
|
-
relevanceScore?: number;
|
|
2822
|
-
}> = [];
|
|
2823
|
-
|
|
2824
|
-
// Parse DuckDuckGo results
|
|
2825
|
-
if (data.RelatedTopics && Array.isArray(data.RelatedTopics)) {
|
|
2826
|
-
for (const topic of data.RelatedTopics.slice(0, maxResults)) {
|
|
2827
|
-
if (topic.FirstURL && topic.Text) {
|
|
2828
|
-
const url = new URL(topic.FirstURL);
|
|
2829
|
-
results.push({
|
|
2830
|
-
title: topic.Text.split(' - ')[0] || topic.Text.substring(0, 60),
|
|
2831
|
-
url: topic.FirstURL,
|
|
2832
|
-
snippet: topic.Text,
|
|
2833
|
-
domain: url.hostname,
|
|
2834
|
-
relevanceScore: Math.random() * 100, // Placeholder scoring
|
|
2835
|
-
});
|
|
2836
|
-
}
|
|
2837
|
-
}
|
|
2838
|
-
}
|
|
2839
|
-
|
|
2840
|
-
// Add abstract as first result if available
|
|
2841
|
-
if (data.Abstract && data.AbstractURL) {
|
|
2842
|
-
const url = new URL(data.AbstractURL);
|
|
2843
|
-
results.unshift({
|
|
2844
|
-
title: data.Heading || 'Main Result',
|
|
2845
|
-
url: data.AbstractURL,
|
|
2846
|
-
snippet: data.Abstract,
|
|
2847
|
-
domain: url.hostname,
|
|
2848
|
-
relevanceScore: 100,
|
|
2849
|
-
});
|
|
2850
|
-
}
|
|
2851
|
-
|
|
2852
|
-
const searchTime = Date.now() - startTime;
|
|
2853
|
-
|
|
2854
|
-
return {
|
|
2855
|
-
success: true,
|
|
2856
|
-
query,
|
|
2857
|
-
results: results.slice(0, maxResults),
|
|
2858
|
-
totalResults: results.length,
|
|
2859
|
-
searchTime,
|
|
2860
|
-
suggestions:
|
|
2861
|
-
data.RelatedTopics?.slice(maxResults, maxResults + 3)
|
|
2862
|
-
?.map((t: any) => t.Text?.split(' - ')[0] || t.Text?.substring(0, 30))
|
|
2863
|
-
.filter(Boolean) || [],
|
|
2864
|
-
};
|
|
2865
|
-
} catch (error) {
|
|
2866
|
-
return {
|
|
2867
|
-
success: false,
|
|
2868
|
-
query: context.query,
|
|
2869
|
-
results: [],
|
|
2870
|
-
totalResults: 0,
|
|
2871
|
-
searchTime: 0,
|
|
2872
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2873
|
-
};
|
|
2874
|
-
}
|
|
2875
|
-
}
|
|
2876
|
-
}
|