@push.rocks/smartagent 1.2.2 → 1.2.4
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/dist_ts/smartagent.classes.driveragent.d.ts +11 -1
- package/dist_ts/smartagent.classes.driveragent.js +26 -5
- package/dist_ts/smartagent.classes.dualagent.d.ts +8 -0
- package/dist_ts/smartagent.classes.dualagent.js +161 -6
- package/dist_ts/smartagent.interfaces.d.ts +43 -0
- package/dist_ts/smartagent.interfaces.js +1 -1
- package/dist_ts/smartagent.tools.filesystem.js +235 -7
- package/package.json +1 -1
- package/readme.md +153 -29
- package/ts/smartagent.classes.driveragent.ts +35 -4
- package/ts/smartagent.classes.dualagent.ts +174 -5
- package/ts/smartagent.interfaces.ts +62 -0
- package/ts/smartagent.tools.filesystem.ts +272 -6
|
@@ -26,6 +26,14 @@ export interface IDualAgentOptions extends plugins.smartai.ISmartAiOptions {
|
|
|
26
26
|
maxConsecutiveRejections?: number;
|
|
27
27
|
/** Enable verbose logging */
|
|
28
28
|
verbose?: boolean;
|
|
29
|
+
/** Maximum characters for tool result output before truncation (default: 15000). Set to 0 to disable. */
|
|
30
|
+
maxResultChars?: number;
|
|
31
|
+
/** Maximum history messages to pass to API (default: 20). Set to 0 for unlimited. */
|
|
32
|
+
maxHistoryMessages?: number;
|
|
33
|
+
/** Optional callback for live progress updates during execution */
|
|
34
|
+
onProgress?: (event: IProgressEvent) => void;
|
|
35
|
+
/** Prefix for log messages (e.g., "[README]", "[Commit]"). Default: empty */
|
|
36
|
+
logPrefix?: string;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
// ================================
|
|
@@ -84,6 +92,8 @@ export interface IToolExecutionResult {
|
|
|
84
92
|
success: boolean;
|
|
85
93
|
result?: unknown;
|
|
86
94
|
error?: string;
|
|
95
|
+
/** Optional human-readable summary for history (if provided, used instead of full result) */
|
|
96
|
+
summary?: string;
|
|
87
97
|
}
|
|
88
98
|
|
|
89
99
|
/**
|
|
@@ -195,6 +205,58 @@ export interface IDualAgentRunResult {
|
|
|
195
205
|
error?: string;
|
|
196
206
|
}
|
|
197
207
|
|
|
208
|
+
// ================================
|
|
209
|
+
// Progress Event Interfaces
|
|
210
|
+
// ================================
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Progress event types for live feedback during agent execution
|
|
214
|
+
*/
|
|
215
|
+
export type TProgressEventType =
|
|
216
|
+
| 'task_started'
|
|
217
|
+
| 'iteration_started'
|
|
218
|
+
| 'tool_proposed'
|
|
219
|
+
| 'guardian_evaluating'
|
|
220
|
+
| 'tool_approved'
|
|
221
|
+
| 'tool_rejected'
|
|
222
|
+
| 'tool_executing'
|
|
223
|
+
| 'tool_completed'
|
|
224
|
+
| 'task_completed'
|
|
225
|
+
| 'clarification_needed'
|
|
226
|
+
| 'max_iterations'
|
|
227
|
+
| 'max_rejections';
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Log level for progress events
|
|
231
|
+
*/
|
|
232
|
+
export type TLogLevel = 'info' | 'warn' | 'error' | 'success';
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Progress event for live feedback during agent execution
|
|
236
|
+
*/
|
|
237
|
+
export interface IProgressEvent {
|
|
238
|
+
/** Type of progress event */
|
|
239
|
+
type: TProgressEventType;
|
|
240
|
+
/** Current iteration number */
|
|
241
|
+
iteration?: number;
|
|
242
|
+
/** Maximum iterations configured */
|
|
243
|
+
maxIterations?: number;
|
|
244
|
+
/** Name of the tool being used */
|
|
245
|
+
toolName?: string;
|
|
246
|
+
/** Action being performed */
|
|
247
|
+
action?: string;
|
|
248
|
+
/** Reason for rejection or other explanation */
|
|
249
|
+
reason?: string;
|
|
250
|
+
/** Human-readable message about the event */
|
|
251
|
+
message?: string;
|
|
252
|
+
/** Timestamp of the event */
|
|
253
|
+
timestamp: Date;
|
|
254
|
+
/** Log level for this event (info, warn, error, success) */
|
|
255
|
+
logLevel: TLogLevel;
|
|
256
|
+
/** Pre-formatted log message ready for output */
|
|
257
|
+
logMessage: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
198
260
|
// ================================
|
|
199
261
|
// Utility Types
|
|
200
262
|
// ================================
|
|
@@ -46,17 +46,25 @@ export class FilesystemTool extends BaseToolWrapper {
|
|
|
46
46
|
public actions: interfaces.IToolAction[] = [
|
|
47
47
|
{
|
|
48
48
|
name: 'read',
|
|
49
|
-
description: 'Read
|
|
49
|
+
description: 'Read file contents (full or specific line range)',
|
|
50
50
|
parameters: {
|
|
51
51
|
type: 'object',
|
|
52
52
|
properties: {
|
|
53
|
-
path: { type: 'string', description: '
|
|
53
|
+
path: { type: 'string', description: 'Path to the file' },
|
|
54
54
|
encoding: {
|
|
55
55
|
type: 'string',
|
|
56
56
|
enum: ['utf8', 'binary', 'base64'],
|
|
57
57
|
default: 'utf8',
|
|
58
58
|
description: 'File encoding',
|
|
59
59
|
},
|
|
60
|
+
startLine: {
|
|
61
|
+
type: 'number',
|
|
62
|
+
description: 'First line to read (1-indexed, inclusive). If omitted, reads from beginning.',
|
|
63
|
+
},
|
|
64
|
+
endLine: {
|
|
65
|
+
type: 'number',
|
|
66
|
+
description: 'Last line to read (1-indexed, inclusive). If omitted, reads to end.',
|
|
67
|
+
},
|
|
60
68
|
},
|
|
61
69
|
required: ['path'],
|
|
62
70
|
},
|
|
@@ -182,6 +190,55 @@ export class FilesystemTool extends BaseToolWrapper {
|
|
|
182
190
|
required: ['path'],
|
|
183
191
|
},
|
|
184
192
|
},
|
|
193
|
+
{
|
|
194
|
+
name: 'tree',
|
|
195
|
+
description: 'Show directory structure as a tree (no file contents)',
|
|
196
|
+
parameters: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
path: { type: 'string', description: 'Root directory path' },
|
|
200
|
+
maxDepth: {
|
|
201
|
+
type: 'number',
|
|
202
|
+
default: 3,
|
|
203
|
+
description: 'Maximum depth to traverse (default: 3)',
|
|
204
|
+
},
|
|
205
|
+
filter: {
|
|
206
|
+
type: 'string',
|
|
207
|
+
description: 'Glob pattern to filter files (e.g., "*.ts")',
|
|
208
|
+
},
|
|
209
|
+
showSizes: {
|
|
210
|
+
type: 'boolean',
|
|
211
|
+
default: false,
|
|
212
|
+
description: 'Include file sizes in output',
|
|
213
|
+
},
|
|
214
|
+
format: {
|
|
215
|
+
type: 'string',
|
|
216
|
+
enum: ['string', 'json'],
|
|
217
|
+
default: 'string',
|
|
218
|
+
description: 'Output format: "string" for human-readable tree, "json" for structured array',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
required: ['path'],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'glob',
|
|
226
|
+
description: 'Find files matching a glob pattern',
|
|
227
|
+
parameters: {
|
|
228
|
+
type: 'object',
|
|
229
|
+
properties: {
|
|
230
|
+
pattern: {
|
|
231
|
+
type: 'string',
|
|
232
|
+
description: 'Glob pattern (e.g., "**/*.ts", "src/**/*.js")',
|
|
233
|
+
},
|
|
234
|
+
path: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
description: 'Base path to search from (defaults to current directory)',
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
required: ['pattern'],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
185
242
|
];
|
|
186
243
|
|
|
187
244
|
private smartfs!: plugins.smartfs.SmartFs;
|
|
@@ -207,16 +264,61 @@ export class FilesystemTool extends BaseToolWrapper {
|
|
|
207
264
|
case 'read': {
|
|
208
265
|
const validatedPath = this.validatePath(params.path as string);
|
|
209
266
|
const encoding = (params.encoding as string) || 'utf8';
|
|
210
|
-
const
|
|
267
|
+
const startLine = params.startLine as number | undefined;
|
|
268
|
+
const endLine = params.endLine as number | undefined;
|
|
269
|
+
|
|
270
|
+
const fullContent = await this.smartfs
|
|
211
271
|
.file(validatedPath)
|
|
212
272
|
.encoding(encoding as 'utf8' | 'binary' | 'base64')
|
|
213
273
|
.read();
|
|
274
|
+
|
|
275
|
+
const contentStr = fullContent.toString();
|
|
276
|
+
const lines = contentStr.split('\n');
|
|
277
|
+
const totalLines = lines.length;
|
|
278
|
+
|
|
279
|
+
// Apply line range if specified
|
|
280
|
+
let resultContent: string;
|
|
281
|
+
let resultStartLine = 1;
|
|
282
|
+
let resultEndLine = totalLines;
|
|
283
|
+
|
|
284
|
+
if (startLine !== undefined || endLine !== undefined) {
|
|
285
|
+
const start = Math.max(1, startLine ?? 1);
|
|
286
|
+
const end = Math.min(totalLines, endLine ?? totalLines);
|
|
287
|
+
resultStartLine = start;
|
|
288
|
+
resultEndLine = end;
|
|
289
|
+
|
|
290
|
+
// Convert to 0-indexed for array slicing
|
|
291
|
+
const selectedLines = lines.slice(start - 1, end);
|
|
292
|
+
|
|
293
|
+
// Add line numbers to output for context
|
|
294
|
+
resultContent = selectedLines
|
|
295
|
+
.map((line, idx) => `${String(start + idx).padStart(5)}│ ${line}`)
|
|
296
|
+
.join('\n');
|
|
297
|
+
} else {
|
|
298
|
+
// No range specified - return full content but warn if large
|
|
299
|
+
const MAX_LINES_WITHOUT_RANGE = 500;
|
|
300
|
+
if (totalLines > MAX_LINES_WITHOUT_RANGE) {
|
|
301
|
+
// Return first portion with warning
|
|
302
|
+
const selectedLines = lines.slice(0, MAX_LINES_WITHOUT_RANGE);
|
|
303
|
+
resultContent = selectedLines
|
|
304
|
+
.map((line, idx) => `${String(idx + 1).padStart(5)}│ ${line}`)
|
|
305
|
+
.join('\n');
|
|
306
|
+
resultContent += `\n\n[... ${totalLines - MAX_LINES_WITHOUT_RANGE} more lines. Use startLine/endLine to read specific ranges.]`;
|
|
307
|
+
resultEndLine = MAX_LINES_WITHOUT_RANGE;
|
|
308
|
+
} else {
|
|
309
|
+
resultContent = contentStr;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
214
313
|
return {
|
|
215
314
|
success: true,
|
|
216
315
|
result: {
|
|
217
316
|
path: params.path,
|
|
218
|
-
content:
|
|
317
|
+
content: resultContent,
|
|
219
318
|
encoding,
|
|
319
|
+
totalLines,
|
|
320
|
+
startLine: resultStartLine,
|
|
321
|
+
endLine: resultEndLine,
|
|
220
322
|
},
|
|
221
323
|
};
|
|
222
324
|
}
|
|
@@ -364,6 +466,160 @@ export class FilesystemTool extends BaseToolWrapper {
|
|
|
364
466
|
};
|
|
365
467
|
}
|
|
366
468
|
|
|
469
|
+
case 'tree': {
|
|
470
|
+
const validatedPath = this.validatePath(params.path as string);
|
|
471
|
+
const maxDepth = (params.maxDepth as number) ?? 3;
|
|
472
|
+
const filter = params.filter as string | undefined;
|
|
473
|
+
const showSizes = (params.showSizes as boolean) ?? false;
|
|
474
|
+
const format = (params.format as 'string' | 'json') ?? 'string';
|
|
475
|
+
|
|
476
|
+
// Collect all entries recursively up to maxDepth
|
|
477
|
+
interface ITreeEntry {
|
|
478
|
+
path: string;
|
|
479
|
+
relativePath: string;
|
|
480
|
+
isDir: boolean;
|
|
481
|
+
depth: number;
|
|
482
|
+
size?: number;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const entries: ITreeEntry[] = [];
|
|
486
|
+
|
|
487
|
+
const collectEntries = async (dirPath: string, depth: number, relativePath: string) => {
|
|
488
|
+
if (depth > maxDepth) return;
|
|
489
|
+
|
|
490
|
+
let dir = this.smartfs.directory(dirPath);
|
|
491
|
+
if (filter) {
|
|
492
|
+
dir = dir.filter(filter);
|
|
493
|
+
}
|
|
494
|
+
const items = await dir.list();
|
|
495
|
+
|
|
496
|
+
for (const item of items) {
|
|
497
|
+
// item is IDirectoryEntry with name, path, isFile, isDirectory properties
|
|
498
|
+
const itemPath = item.path;
|
|
499
|
+
const itemRelPath = relativePath ? `${relativePath}/${item.name}` : item.name;
|
|
500
|
+
const isDir = item.isDirectory;
|
|
501
|
+
|
|
502
|
+
const entry: ITreeEntry = {
|
|
503
|
+
path: itemPath,
|
|
504
|
+
relativePath: itemRelPath,
|
|
505
|
+
isDir,
|
|
506
|
+
depth,
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
if (showSizes && !isDir && item.stats) {
|
|
510
|
+
entry.size = item.stats.size;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
entries.push(entry);
|
|
514
|
+
|
|
515
|
+
// Recurse into directories
|
|
516
|
+
if (isDir && depth < maxDepth) {
|
|
517
|
+
await collectEntries(itemPath, depth + 1, itemRelPath);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
await collectEntries(validatedPath, 0, '');
|
|
523
|
+
|
|
524
|
+
// Sort entries by path for consistent output
|
|
525
|
+
entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
526
|
+
|
|
527
|
+
if (format === 'json') {
|
|
528
|
+
return {
|
|
529
|
+
success: true,
|
|
530
|
+
result: {
|
|
531
|
+
path: params.path,
|
|
532
|
+
entries: entries.map((e) => ({
|
|
533
|
+
path: e.relativePath,
|
|
534
|
+
isDir: e.isDir,
|
|
535
|
+
depth: e.depth,
|
|
536
|
+
...(e.size !== undefined ? { size: e.size } : {}),
|
|
537
|
+
})),
|
|
538
|
+
count: entries.length,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Format as string tree
|
|
544
|
+
const formatSize = (bytes: number): string => {
|
|
545
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
546
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
547
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// Build tree string with proper indentation
|
|
551
|
+
let treeStr = `${params.path}/\n`;
|
|
552
|
+
const pathParts = new Map<string, number>(); // Track which paths are last in their parent
|
|
553
|
+
|
|
554
|
+
// Group by parent to determine last child
|
|
555
|
+
const parentChildCount = new Map<string, number>();
|
|
556
|
+
const parentCurrentChild = new Map<string, number>();
|
|
557
|
+
|
|
558
|
+
for (const entry of entries) {
|
|
559
|
+
const parentPath = entry.relativePath.includes('/')
|
|
560
|
+
? entry.relativePath.substring(0, entry.relativePath.lastIndexOf('/'))
|
|
561
|
+
: '';
|
|
562
|
+
parentChildCount.set(parentPath, (parentChildCount.get(parentPath) || 0) + 1);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
for (const entry of entries) {
|
|
566
|
+
const parentPath = entry.relativePath.includes('/')
|
|
567
|
+
? entry.relativePath.substring(0, entry.relativePath.lastIndexOf('/'))
|
|
568
|
+
: '';
|
|
569
|
+
parentCurrentChild.set(parentPath, (parentCurrentChild.get(parentPath) || 0) + 1);
|
|
570
|
+
const isLast = parentCurrentChild.get(parentPath) === parentChildCount.get(parentPath);
|
|
571
|
+
|
|
572
|
+
// Build prefix based on depth
|
|
573
|
+
let prefix = '';
|
|
574
|
+
const parts = entry.relativePath.split('/');
|
|
575
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
576
|
+
prefix += '│ ';
|
|
577
|
+
}
|
|
578
|
+
prefix += isLast ? '└── ' : '├── ';
|
|
579
|
+
|
|
580
|
+
const name = parts[parts.length - 1];
|
|
581
|
+
const suffix = entry.isDir ? '/' : '';
|
|
582
|
+
const sizeStr = showSizes && entry.size !== undefined ? ` (${formatSize(entry.size)})` : '';
|
|
583
|
+
|
|
584
|
+
treeStr += `${prefix}${name}${suffix}${sizeStr}\n`;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
success: true,
|
|
589
|
+
result: {
|
|
590
|
+
path: params.path,
|
|
591
|
+
tree: treeStr,
|
|
592
|
+
count: entries.length,
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
case 'glob': {
|
|
598
|
+
const pattern = params.pattern as string;
|
|
599
|
+
const basePath = params.path ? this.validatePath(params.path as string) : (this.basePath || process.cwd());
|
|
600
|
+
|
|
601
|
+
// Use smartfs to list with filter
|
|
602
|
+
const dir = this.smartfs.directory(basePath).recursive().filter(pattern);
|
|
603
|
+
const matches = await dir.list();
|
|
604
|
+
|
|
605
|
+
// Return file paths relative to base path for readability
|
|
606
|
+
const files = matches.map((entry) => ({
|
|
607
|
+
path: entry.path,
|
|
608
|
+
relativePath: plugins.path.relative(basePath, entry.path),
|
|
609
|
+
isDirectory: entry.isDirectory,
|
|
610
|
+
}));
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
success: true,
|
|
614
|
+
result: {
|
|
615
|
+
pattern,
|
|
616
|
+
basePath,
|
|
617
|
+
files,
|
|
618
|
+
count: files.length,
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
367
623
|
default:
|
|
368
624
|
return {
|
|
369
625
|
success: false,
|
|
@@ -380,8 +636,12 @@ export class FilesystemTool extends BaseToolWrapper {
|
|
|
380
636
|
|
|
381
637
|
public getCallSummary(action: string, params: Record<string, unknown>): string {
|
|
382
638
|
switch (action) {
|
|
383
|
-
case 'read':
|
|
384
|
-
|
|
639
|
+
case 'read': {
|
|
640
|
+
const lineRange = params.startLine || params.endLine
|
|
641
|
+
? ` lines ${params.startLine || 1}-${params.endLine || 'end'}`
|
|
642
|
+
: '';
|
|
643
|
+
return `Read file "${params.path}"${lineRange}`;
|
|
644
|
+
}
|
|
385
645
|
|
|
386
646
|
case 'write': {
|
|
387
647
|
const content = params.content as string;
|
|
@@ -416,6 +676,12 @@ export class FilesystemTool extends BaseToolWrapper {
|
|
|
416
676
|
case 'mkdir':
|
|
417
677
|
return `Create directory "${params.path}"${params.recursive !== false ? ' (with parents)' : ''}`;
|
|
418
678
|
|
|
679
|
+
case 'tree':
|
|
680
|
+
return `Show tree of "${params.path}" (depth: ${params.maxDepth ?? 3}, format: ${params.format ?? 'string'})`;
|
|
681
|
+
|
|
682
|
+
case 'glob':
|
|
683
|
+
return `Find files matching "${params.pattern}"${params.path ? ` in "${params.path}"` : ''}`;
|
|
684
|
+
|
|
419
685
|
default:
|
|
420
686
|
return `Unknown action: ${action}`;
|
|
421
687
|
}
|