@hanzo/dev 1.2.0 → 2.1.0

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.
@@ -0,0 +1,378 @@
1
+ import { FileEditor, ChunkLocalizer } from './editor';
2
+ import { FunctionCallingSystem, FunctionCall } from './function-calling';
3
+ import { spawn } from 'child_process';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import chalk from 'chalk';
7
+
8
+ export interface AgentTask {
9
+ id: string;
10
+ description: string;
11
+ status: 'pending' | 'running' | 'completed' | 'failed';
12
+ result?: any;
13
+ error?: string;
14
+ retries: number;
15
+ }
16
+
17
+ export interface CodeActPlan {
18
+ steps: string[];
19
+ parallelizable: boolean[];
20
+ currentStep: number;
21
+ }
22
+
23
+ export class CodeActAgent {
24
+ private editor: FileEditor;
25
+ private functionCalling: FunctionCallingSystem;
26
+ private tasks: Map<string, AgentTask> = new Map();
27
+ private maxRetries: number = 3;
28
+ private parallelExecutor: ParallelExecutor;
29
+
30
+ constructor() {
31
+ this.editor = new FileEditor();
32
+ this.functionCalling = new FunctionCallingSystem();
33
+ this.parallelExecutor = new ParallelExecutor();
34
+ }
35
+
36
+ // Plan and execute a complex task
37
+ async executeTask(description: string): Promise<void> {
38
+ console.log(chalk.cyan(`\nšŸ¤– CodeAct Agent: ${description}\n`));
39
+
40
+ // Generate plan
41
+ const plan = await this.generatePlan(description);
42
+ console.log(chalk.yellow('šŸ“‹ Execution Plan:'));
43
+ plan.steps.forEach((step, i) => {
44
+ const parallel = plan.parallelizable[i] ? ' [parallel]' : '';
45
+ console.log(` ${i + 1}. ${step}${parallel}`);
46
+ });
47
+ console.log();
48
+
49
+ // Execute plan
50
+ await this.executePlan(plan);
51
+ }
52
+
53
+ private async generatePlan(description: string): Promise<CodeActPlan> {
54
+ // In a real implementation, this would use an LLM to generate the plan
55
+ // For now, we'll use pattern matching
56
+ const steps: string[] = [];
57
+ const parallelizable: boolean[] = [];
58
+
59
+ if (description.includes('refactor')) {
60
+ steps.push('Analyze current code structure');
61
+ parallelizable.push(false);
62
+ steps.push('Identify refactoring opportunities');
63
+ parallelizable.push(false);
64
+ steps.push('Apply refactoring changes');
65
+ parallelizable.push(true);
66
+ steps.push('Run tests');
67
+ parallelizable.push(false);
68
+ steps.push('Fix any issues');
69
+ parallelizable.push(false);
70
+ } else if (description.includes('test')) {
71
+ steps.push('Discover test files');
72
+ parallelizable.push(true);
73
+ steps.push('Run tests');
74
+ parallelizable.push(true);
75
+ steps.push('Analyze failures');
76
+ parallelizable.push(false);
77
+ steps.push('Fix failing tests');
78
+ parallelizable.push(true);
79
+ steps.push('Re-run tests');
80
+ parallelizable.push(false);
81
+ } else {
82
+ // Default plan
83
+ steps.push('Analyze requirements');
84
+ parallelizable.push(false);
85
+ steps.push('Implement changes');
86
+ parallelizable.push(false);
87
+ steps.push('Validate changes');
88
+ parallelizable.push(false);
89
+ }
90
+
91
+ return { steps, parallelizable, currentStep: 0 };
92
+ }
93
+
94
+ private async executePlan(plan: CodeActPlan): Promise<void> {
95
+ for (let i = 0; i < plan.steps.length; i++) {
96
+ plan.currentStep = i;
97
+ const step = plan.steps[i];
98
+
99
+ console.log(chalk.blue(`\nā–¶ Step ${i + 1}: ${step}`));
100
+
101
+ if (plan.parallelizable[i] && i + 1 < plan.steps.length && plan.parallelizable[i + 1]) {
102
+ // Collect parallel tasks
103
+ const parallelTasks: string[] = [step];
104
+ while (i + 1 < plan.steps.length && plan.parallelizable[i + 1]) {
105
+ i++;
106
+ parallelTasks.push(plan.steps[i]);
107
+ }
108
+
109
+ // Execute in parallel
110
+ await this.executeParallelSteps(parallelTasks);
111
+ } else {
112
+ // Execute single step
113
+ await this.executeStep(step);
114
+ }
115
+ }
116
+
117
+ console.log(chalk.green('\nāœ… Task completed successfully!\n'));
118
+ }
119
+
120
+ private async executeStep(step: string): Promise<void> {
121
+ const taskId = this.generateTaskId();
122
+ const task: AgentTask = {
123
+ id: taskId,
124
+ description: step,
125
+ status: 'running',
126
+ retries: 0
127
+ };
128
+
129
+ this.tasks.set(taskId, task);
130
+
131
+ try {
132
+ // Execute with retry logic
133
+ await this.executeWithRetry(task);
134
+ task.status = 'completed';
135
+ console.log(chalk.green(` āœ“ ${step}`));
136
+ } catch (error) {
137
+ task.status = 'failed';
138
+ task.error = error instanceof Error ? error.message : String(error);
139
+ console.log(chalk.red(` āœ— ${step}: ${task.error}`));
140
+ throw error;
141
+ }
142
+ }
143
+
144
+ private async executeParallelSteps(steps: string[]): Promise<void> {
145
+ console.log(chalk.yellow(`\n⚔ Executing ${steps.length} steps in parallel...`));
146
+
147
+ const tasks = steps.map(step => {
148
+ const taskId = this.generateTaskId();
149
+ const task: AgentTask = {
150
+ id: taskId,
151
+ description: step,
152
+ status: 'pending',
153
+ retries: 0
154
+ };
155
+ this.tasks.set(taskId, task);
156
+ return task;
157
+ });
158
+
159
+ const results = await this.parallelExecutor.executeTasks(tasks, async (task) => {
160
+ task.status = 'running';
161
+ await this.executeWithRetry(task);
162
+ task.status = 'completed';
163
+ console.log(chalk.green(` āœ“ ${task.description}`));
164
+ });
165
+
166
+ // Check for failures
167
+ const failures = results.filter(r => r.status === 'failed');
168
+ if (failures.length > 0) {
169
+ throw new Error(`${failures.length} parallel tasks failed`);
170
+ }
171
+ }
172
+
173
+ private async executeWithRetry(task: AgentTask): Promise<void> {
174
+ while (task.retries < this.maxRetries) {
175
+ try {
176
+ // Map step description to actual actions
177
+ await this.mapStepToActions(task.description);
178
+ return;
179
+ } catch (error) {
180
+ task.retries++;
181
+ if (task.retries >= this.maxRetries) {
182
+ throw error;
183
+ }
184
+
185
+ console.log(chalk.yellow(` ⚠ Retry ${task.retries}/${this.maxRetries}: ${error}`));
186
+
187
+ // Attempt to fix the error
188
+ await this.attemptErrorCorrection(error);
189
+ }
190
+ }
191
+ }
192
+
193
+ private async mapStepToActions(step: string): Promise<void> {
194
+ // This would use LLM in real implementation
195
+ // For now, use pattern matching
196
+
197
+ if (step.includes('test')) {
198
+ await this.runTests();
199
+ } else if (step.includes('analyze') || step.includes('identify')) {
200
+ await this.analyzeCode();
201
+ } else if (step.includes('refactor') || step.includes('implement')) {
202
+ await this.implementChanges();
203
+ } else if (step.includes('fix')) {
204
+ await this.fixIssues();
205
+ }
206
+
207
+ // Simulate some work
208
+ await new Promise(resolve => setTimeout(resolve, 500));
209
+ }
210
+
211
+ private async runTests(): Promise<void> {
212
+ const result = await this.functionCalling.callFunction({
213
+ id: Date.now().toString(),
214
+ name: 'run_command',
215
+ arguments: { command: 'npm test' }
216
+ });
217
+
218
+ if (!result.result?.success) {
219
+ throw new Error('Tests failed');
220
+ }
221
+ }
222
+
223
+ private async analyzeCode(): Promise<void> {
224
+ // Use search and analysis tools
225
+ const result = await this.functionCalling.callFunction({
226
+ id: Date.now().toString(),
227
+ name: 'search_files',
228
+ arguments: { pattern: '*.ts', path: '.' }
229
+ });
230
+
231
+ // Analyze results...
232
+ }
233
+
234
+ private async implementChanges(): Promise<void> {
235
+ // Simulate making changes
236
+ console.log(chalk.gray(' Making code changes...'));
237
+ }
238
+
239
+ private async fixIssues(): Promise<void> {
240
+ // Simulate fixing issues
241
+ console.log(chalk.gray(' Applying fixes...'));
242
+ }
243
+
244
+ private async attemptErrorCorrection(error: any): Promise<void> {
245
+ console.log(chalk.yellow(' šŸ”§ Attempting automatic error correction...'));
246
+
247
+ const errorMessage = error instanceof Error ? error.message : String(error);
248
+
249
+ // Pattern-based error correction
250
+ if (errorMessage.includes('Tests failed')) {
251
+ console.log(chalk.gray(' Analyzing test failures...'));
252
+ // Would use LLM to analyze and fix test failures
253
+ } else if (errorMessage.includes('compile')) {
254
+ console.log(chalk.gray(' Fixing compilation errors...'));
255
+ // Would use LLM to fix compilation errors
256
+ } else if (errorMessage.includes('lint')) {
257
+ console.log(chalk.gray(' Fixing linting errors...'));
258
+ await this.functionCalling.callFunction({
259
+ id: Date.now().toString(),
260
+ name: 'run_command',
261
+ arguments: { command: 'npm run lint -- --fix' }
262
+ });
263
+ }
264
+ }
265
+
266
+ private generateTaskId(): string {
267
+ return `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
268
+ }
269
+ }
270
+
271
+ // Parallel task executor
272
+ class ParallelExecutor {
273
+ async executeTasks<T extends AgentTask>(
274
+ tasks: T[],
275
+ executor: (task: T) => Promise<void>
276
+ ): Promise<T[]> {
277
+ const promises = tasks.map(task =>
278
+ executor(task).catch(error => {
279
+ task.status = 'failed';
280
+ task.error = error instanceof Error ? error.message : String(error);
281
+ })
282
+ );
283
+
284
+ await Promise.all(promises);
285
+ return tasks;
286
+ }
287
+ }
288
+
289
+ // Advanced file editing with AST understanding
290
+ export class SmartFileEditor extends FileEditor {
291
+ private lspClient?: LSPClient;
292
+
293
+ async editWithUnderstanding(
294
+ filePath: string,
295
+ intent: string
296
+ ): Promise<void> {
297
+ console.log(chalk.cyan(`\n🧠 Smart Edit: ${intent}\n`));
298
+
299
+ // Read file
300
+ const content = fs.readFileSync(filePath, 'utf-8');
301
+
302
+ // Find relevant chunk
303
+ const chunk = ChunkLocalizer.findRelevantChunk(content, intent);
304
+ if (!chunk) {
305
+ throw new Error('Could not locate relevant code section');
306
+ }
307
+
308
+ console.log(chalk.gray(`Found relevant code at lines ${chunk.startLine}-${chunk.endLine}`));
309
+
310
+ // In real implementation, would use LLM to generate the edit
311
+ // For now, just show what would happen
312
+ console.log(chalk.yellow('Would apply intelligent edit based on intent'));
313
+ }
314
+
315
+ async applyBulkRefactoring(
316
+ pattern: string,
317
+ transformation: string
318
+ ): Promise<void> {
319
+ console.log(chalk.cyan(`\nšŸ”„ Bulk Refactoring: ${pattern} → ${transformation}\n`));
320
+
321
+ // Find all files matching pattern
322
+ const files = await this.findFilesWithPattern(pattern);
323
+ console.log(chalk.gray(`Found ${files.length} files to refactor`));
324
+
325
+ // Apply transformation to each file
326
+ for (const file of files) {
327
+ console.log(chalk.gray(` Refactoring ${file}...`));
328
+ // Would apply transformation
329
+ }
330
+ }
331
+
332
+ private async findFilesWithPattern(pattern: string): Promise<string[]> {
333
+ // Simplified implementation
334
+ const results: string[] = [];
335
+
336
+ const walkDir = (dir: string) => {
337
+ const files = fs.readdirSync(dir);
338
+ for (const file of files) {
339
+ const fullPath = path.join(dir, file);
340
+ const stats = fs.statSync(fullPath);
341
+
342
+ if (stats.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
343
+ walkDir(fullPath);
344
+ } else if (stats.isFile() && fullPath.endsWith('.ts')) {
345
+ const content = fs.readFileSync(fullPath, 'utf-8');
346
+ if (content.includes(pattern)) {
347
+ results.push(fullPath);
348
+ }
349
+ }
350
+ }
351
+ };
352
+
353
+ walkDir(process.cwd());
354
+ return results;
355
+ }
356
+ }
357
+
358
+ // Placeholder for LSP client (would integrate with real LSP)
359
+ class LSPClient {
360
+ async initialize(rootPath: string): Promise<void> {
361
+ console.log(chalk.gray(`Initializing LSP for ${rootPath}`));
362
+ }
363
+
364
+ async getDefinition(file: string, position: number): Promise<any> {
365
+ // Would return actual definition location
366
+ return null;
367
+ }
368
+
369
+ async getReferences(file: string, position: number): Promise<any[]> {
370
+ // Would return all references
371
+ return [];
372
+ }
373
+
374
+ async rename(file: string, position: number, newName: string): Promise<any> {
375
+ // Would perform project-wide rename
376
+ return null;
377
+ }
378
+ }
@@ -0,0 +1,163 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import * as toml from '@iarna/toml';
5
+
6
+ export interface LLMConfig {
7
+ model: string;
8
+ apiKey?: string;
9
+ baseUrl?: string;
10
+ temperature?: number;
11
+ maxTokens?: number;
12
+ provider?: 'openai' | 'anthropic' | 'google' | 'azure' | 'local';
13
+ }
14
+
15
+ export interface AgentConfig {
16
+ name: string;
17
+ llmConfig?: LLMConfig;
18
+ memoryEnabled?: boolean;
19
+ microagentsEnabled?: boolean;
20
+ }
21
+
22
+ export interface SecurityConfig {
23
+ confirmationMode: boolean;
24
+ sandboxMode?: boolean;
25
+ allowedCommands?: string[];
26
+ }
27
+
28
+ export interface SandboxConfig {
29
+ workspaceBase: string;
30
+ selectedRepo?: string;
31
+ useHost?: boolean;
32
+ }
33
+
34
+ export interface HanzoDevConfig {
35
+ // Core settings
36
+ defaultAgent: string;
37
+ agents: AgentConfig[];
38
+ llm: LLMConfig;
39
+ security: SecurityConfig;
40
+ sandbox: SandboxConfig;
41
+
42
+ // CLI specific
43
+ cliMultilineInput?: boolean;
44
+ runtime?: 'cli' | 'docker' | 'local';
45
+
46
+ // Session
47
+ sessionName?: string;
48
+ resumeSession?: boolean;
49
+ }
50
+
51
+ export class ConfigManager {
52
+ private configPath: string;
53
+ private config: HanzoDevConfig;
54
+
55
+ constructor() {
56
+ this.configPath = this.getConfigPath();
57
+ this.config = this.loadConfig();
58
+ }
59
+
60
+ private getConfigPath(): string {
61
+ // Check for config in order of precedence
62
+ const locations = [
63
+ path.join(process.cwd(), 'hanzo-dev.toml'),
64
+ path.join(process.cwd(), '.hanzo-dev', 'config.toml'),
65
+ path.join(os.homedir(), '.config', 'hanzo-dev', 'config.toml'),
66
+ ];
67
+
68
+ for (const loc of locations) {
69
+ if (fs.existsSync(loc)) {
70
+ return loc;
71
+ }
72
+ }
73
+
74
+ // Default location
75
+ return path.join(os.homedir(), '.config', 'hanzo-dev', 'config.toml');
76
+ }
77
+
78
+ private loadConfig(): HanzoDevConfig {
79
+ // Default configuration
80
+ const defaultConfig: HanzoDevConfig = {
81
+ defaultAgent: 'CodeActAgent',
82
+ agents: [{
83
+ name: 'CodeActAgent',
84
+ memoryEnabled: true,
85
+ microagentsEnabled: true,
86
+ }],
87
+ llm: {
88
+ model: 'gpt-4',
89
+ provider: 'openai',
90
+ temperature: 0.7,
91
+ },
92
+ security: {
93
+ confirmationMode: true,
94
+ sandboxMode: true,
95
+ },
96
+ sandbox: {
97
+ workspaceBase: process.cwd(),
98
+ useHost: false,
99
+ },
100
+ runtime: 'cli',
101
+ cliMultilineInput: false,
102
+ };
103
+
104
+ // Load from file if exists
105
+ if (fs.existsSync(this.configPath)) {
106
+ try {
107
+ const content = fs.readFileSync(this.configPath, 'utf-8');
108
+ const parsed = toml.parse(content) as Partial<HanzoDevConfig>;
109
+ return { ...defaultConfig, ...parsed };
110
+ } catch (error) {
111
+ console.error('Error loading config:', error);
112
+ return defaultConfig;
113
+ }
114
+ }
115
+
116
+ return defaultConfig;
117
+ }
118
+
119
+ public getConfig(): HanzoDevConfig {
120
+ return this.config;
121
+ }
122
+
123
+ public updateConfig(updates: Partial<HanzoDevConfig>): void {
124
+ this.config = { ...this.config, ...updates };
125
+ this.saveConfig();
126
+ }
127
+
128
+ private saveConfig(): void {
129
+ const configDir = path.dirname(this.configPath);
130
+ if (!fs.existsSync(configDir)) {
131
+ fs.mkdirSync(configDir, { recursive: true });
132
+ }
133
+
134
+ const content = toml.stringify(this.config as any);
135
+ fs.writeFileSync(this.configPath, content);
136
+ }
137
+
138
+ // Load LLM config from environment
139
+ public loadLLMConfigFromEnv(): LLMConfig {
140
+ const config = this.config.llm;
141
+
142
+ // Check for API keys in environment
143
+ if (process.env.OPENAI_API_KEY) {
144
+ config.apiKey = process.env.OPENAI_API_KEY;
145
+ config.provider = 'openai';
146
+ } else if (process.env.ANTHROPIC_API_KEY) {
147
+ config.apiKey = process.env.ANTHROPIC_API_KEY;
148
+ config.provider = 'anthropic';
149
+ config.model = 'claude-3-sonnet-20240229';
150
+ } else if (process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY) {
151
+ config.apiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
152
+ config.provider = 'google';
153
+ config.model = 'gemini-pro';
154
+ }
155
+
156
+ // Check for base URL
157
+ if (process.env.LLM_BASE_URL) {
158
+ config.baseUrl = process.env.LLM_BASE_URL;
159
+ }
160
+
161
+ return config;
162
+ }
163
+ }