minovative-mind-cli 2.7.0 → 2.8.1
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/README.md +1 -1
- package/dist/services/agent/slashCommands.js +12 -5
- package/dist/services/agent/toolLoop.js +52 -2
- package/dist/services/agent-tools.d.ts +26 -0
- package/dist/services/agent-tools.js +255 -0
- package/dist/services/agent.js +10 -5
- package/dist/services/orchestration/investigationOrchestrator.js +3 -0
- package/dist/services/orchestration/scopedTools.js +26 -0
- package/dist/services/orchestration/subAgent.js +1 -1
- package/dist/services/proxyClient.js +7 -4
- package/dist/utils/analysisRunner.d.ts +65 -0
- package/dist/utils/analysisRunner.js +558 -0
- package/dist/utils/config.d.ts +13 -0
- package/dist/utils/config.js +13 -0
- package/dist/utils/systemPrompts.d.ts +1 -1
- package/dist/utils/systemPrompts.js +6 -1
- package/oclif.manifest.json +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -165,7 +165,7 @@ _⚠️\*Support is limited or requires custom local system tooling/environment
|
|
|
165
165
|
|
|
166
166
|
1. **Dependency Tracing:** Fully supported across language families. It uses lightning-fast static regex pattern matching to resolve local import structures and determine the "blast radius" of code changes without requiring local compilations.
|
|
167
167
|
2. **Performance Auditing:** Executed natively across 9 major language families (JavaScript/TypeScript, Python, Go, Rust, PHP, C#, Java, C/C++, and Ruby). It uses zero-dependency, ultra-fast (<50ms) language-aware regex heuristics with comment/string stripping and line-preserving offset tracking to detect severe runtime anti-patterns (such as unbounded loops, synchronous I/O, chained array allocations, unnecessary allocations, and unclosed resources).
|
|
168
|
-
3. **Ephemeral Analysis & Property-Based Probing:**
|
|
168
|
+
3. **Ephemeral Analysis & Property-Based Probing:** Defaults to Node.js as a safe baseline, but natively leverages host-installed runtimes (Python, Rust, Go compilers) and standard libraries whenever available in the workspace.
|
|
169
169
|
|
|
170
170
|
---
|
|
171
171
|
|
|
@@ -223,20 +223,27 @@ export async function handleSlashCommand(command, context) {
|
|
|
223
223
|
try {
|
|
224
224
|
const { getInvestigationCacheStats } = await import('../orchestration/investigationCache.js');
|
|
225
225
|
const invStats = getInvestigationCacheStats(workspaceRoot);
|
|
226
|
-
const
|
|
227
|
-
|
|
226
|
+
const formatBytesToSize = (bytes) => {
|
|
227
|
+
if (bytes >= 1024 * 1024) {
|
|
228
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
229
|
+
}
|
|
230
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
231
|
+
};
|
|
232
|
+
const memBankSizeStr = formatBytesToSize(invStats.sizeBytes);
|
|
233
|
+
console.log(`${pc.bold('Memory Bank:')} ${pc.cyan(`${invStats.entries} cached investigations (${memBankSizeStr} / 5 MB)`)}`);
|
|
228
234
|
const { readCache } = await import('../../utils/projectStorage.js');
|
|
229
235
|
const contextCache = readCache(workspaceRoot, 'context_cache.json');
|
|
230
236
|
const contextEntries = contextCache ? Object.keys(contextCache).length : 0;
|
|
231
|
-
let
|
|
237
|
+
let contextSizeBytes = 0;
|
|
232
238
|
try {
|
|
233
239
|
const fs = await import('node:fs');
|
|
234
240
|
const path = await import('node:path');
|
|
235
241
|
const stat = await fs.promises.stat(path.join(workspaceRoot, '.minovativemind', 'context_cache.json'));
|
|
236
|
-
|
|
242
|
+
contextSizeBytes = stat.size;
|
|
237
243
|
}
|
|
238
244
|
catch (e) { }
|
|
239
|
-
|
|
245
|
+
const contextSizeStr = formatBytesToSize(contextSizeBytes);
|
|
246
|
+
console.log(`${pc.bold('Context Cache:')} ${pc.cyan(`${contextEntries} file summaries (${contextSizeStr} / 5 MB)`)}`);
|
|
240
247
|
}
|
|
241
248
|
catch (e) {
|
|
242
249
|
// Ignored if cache stats fail
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import * as p from '@clack/prompts';
|
|
2
3
|
import pc from 'picocolors';
|
|
3
4
|
import { debugLog } from '../../utils/logger.js';
|
|
@@ -24,6 +25,9 @@ const TOOL_ICONS = {
|
|
|
24
25
|
find_dependencies: '🔗',
|
|
25
26
|
run_analysis_script: '🔬',
|
|
26
27
|
run_debug_script: '🧪',
|
|
28
|
+
run_fuzz_probe: '🎯',
|
|
29
|
+
check_heap_delta: '📊',
|
|
30
|
+
check_behavioral_drift: '🔀',
|
|
27
31
|
};
|
|
28
32
|
/**
|
|
29
33
|
* Human-readable translations for tool actions.
|
|
@@ -41,6 +45,9 @@ const TOOL_LABELS = {
|
|
|
41
45
|
find_dependencies: 'Tracing dependencies',
|
|
42
46
|
run_analysis_script: 'Analyzing workspace',
|
|
43
47
|
run_debug_script: 'Executing debug/validation script',
|
|
48
|
+
run_fuzz_probe: 'Executing fuzz testing probe',
|
|
49
|
+
check_heap_delta: 'Inspecting memory heap delta',
|
|
50
|
+
check_behavioral_drift: 'Comparing behavioral drift',
|
|
44
51
|
};
|
|
45
52
|
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
46
53
|
/**
|
|
@@ -52,8 +59,20 @@ const TOOL_LABELS = {
|
|
|
52
59
|
* @returns A fully colorized and formatted ANSI console string.
|
|
53
60
|
*/
|
|
54
61
|
export function formatToolCall(name, args) {
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
let icon = TOOL_ICONS[name] ?? '🔧';
|
|
63
|
+
let label = TOOL_LABELS[name] ?? name;
|
|
64
|
+
if (name === 'rename_file') {
|
|
65
|
+
const src = String(args.sourcePath ?? '');
|
|
66
|
+
const tgt = String(args.targetPath ?? '');
|
|
67
|
+
if (src && tgt && path.dirname(src) === path.dirname(tgt)) {
|
|
68
|
+
label = 'Renaming file';
|
|
69
|
+
icon = '🏷️';
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
label = 'Moving file';
|
|
73
|
+
icon = '🚚';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
57
76
|
const formatPath = (path) => pc.cyan(path);
|
|
58
77
|
const argsMap = {
|
|
59
78
|
read_file: () => {
|
|
@@ -86,6 +105,37 @@ export function formatToolCall(name, args) {
|
|
|
86
105
|
find_dependencies: () => `: ${formatPath(String(args.filePath))}${args.direction ? ` (${args.direction})` : ''}`,
|
|
87
106
|
run_analysis_script: () => `: ${formatPath(String(args.targetFile ?? 'workspace'))}`,
|
|
88
107
|
run_debug_script: () => `: ${pc.dim(`(${String(args.language ?? 'script')})`)}`,
|
|
108
|
+
run_fuzz_probe: () => {
|
|
109
|
+
const lang = String(args.language ?? 'node');
|
|
110
|
+
const extras = [];
|
|
111
|
+
if (args.iterations !== undefined)
|
|
112
|
+
extras.push(`${args.iterations} iterations`);
|
|
113
|
+
if (args.seed !== undefined)
|
|
114
|
+
extras.push(`seed: ${args.seed}`);
|
|
115
|
+
const meta = extras.length > 0 ? `, ${extras.join(', ')}` : '';
|
|
116
|
+
return `: ${pc.dim(`(${lang}${meta})`)}`;
|
|
117
|
+
},
|
|
118
|
+
check_heap_delta: () => {
|
|
119
|
+
const lang = String(args.language ?? 'node');
|
|
120
|
+
const extras = [];
|
|
121
|
+
if (args.testIterations !== undefined)
|
|
122
|
+
extras.push(`${args.testIterations} iterations`);
|
|
123
|
+
if (args.samplingIntervalMs !== undefined)
|
|
124
|
+
extras.push(`${args.samplingIntervalMs}ms interval`);
|
|
125
|
+
const meta = extras.length > 0 ? `, ${extras.join(', ')}` : '';
|
|
126
|
+
return `: ${pc.dim(`(${lang}${meta})`)}`;
|
|
127
|
+
},
|
|
128
|
+
check_behavioral_drift: () => {
|
|
129
|
+
const lang = String(args.language ?? 'node');
|
|
130
|
+
const extras = [];
|
|
131
|
+
if (args.tolerance !== undefined)
|
|
132
|
+
extras.push(`tol: ${args.tolerance}`);
|
|
133
|
+
if (Array.isArray(args.compareKeys) && args.compareKeys.length > 0) {
|
|
134
|
+
extras.push(`keys: ${args.compareKeys.join(', ')}`);
|
|
135
|
+
}
|
|
136
|
+
const meta = extras.length > 0 ? `, ${extras.join(', ')}` : '';
|
|
137
|
+
return `: ${pc.dim(`(${lang}${meta})`)}`;
|
|
138
|
+
},
|
|
89
139
|
};
|
|
90
140
|
const formatter = argsMap[name];
|
|
91
141
|
const details = formatter ? formatter() : '';
|
|
@@ -183,6 +183,32 @@ export declare function findRecentChanges(workspaceRoot: string, dirPath?: strin
|
|
|
183
183
|
* @returns A promise resolving to a {@link ToolResult} containing execution output.
|
|
184
184
|
*/
|
|
185
185
|
export declare function runDebugScript(workspaceRoot: string, language: string, code: string, abortSignal?: AbortSignal): Promise<ToolResult>;
|
|
186
|
+
/**
|
|
187
|
+
* Executes a fuzz probing analysis script and formats the resulting pass/fail and crash statistics.
|
|
188
|
+
*/
|
|
189
|
+
export declare function executeFuzzProbe(workspaceRoot: string, code: string, language?: string, config?: {
|
|
190
|
+
iterations?: number;
|
|
191
|
+
maxInputLength?: number;
|
|
192
|
+
seed?: number;
|
|
193
|
+
timeoutMs?: number;
|
|
194
|
+
}, abortSignal?: AbortSignal): Promise<ToolResult>;
|
|
195
|
+
/**
|
|
196
|
+
* Executes a heap memory inspection script and formats memory consumption and growth delta metrics.
|
|
197
|
+
*/
|
|
198
|
+
export declare function executeHeapDelta(workspaceRoot: string, code: string, language?: string, config?: {
|
|
199
|
+
warmupIterations?: number;
|
|
200
|
+
testIterations?: number;
|
|
201
|
+
samplingIntervalMs?: number;
|
|
202
|
+
timeoutMs?: number;
|
|
203
|
+
}, abortSignal?: AbortSignal): Promise<ToolResult>;
|
|
204
|
+
/**
|
|
205
|
+
* Executes baseline and candidate scripts, returning output diffs and behavioral drift findings.
|
|
206
|
+
*/
|
|
207
|
+
export declare function executeBehavioralDrift(workspaceRoot: string, baselineCode: string, candidateCode: string, language?: string, config?: {
|
|
208
|
+
tolerance?: number;
|
|
209
|
+
compareKeys?: string[];
|
|
210
|
+
timeoutMs?: number;
|
|
211
|
+
}, abortSignal?: AbortSignal): Promise<ToolResult>;
|
|
186
212
|
/**
|
|
187
213
|
* Central tool dispatcher that resolves multi-workspace paths, executes the requested tool
|
|
188
214
|
* with the provided arguments, records metrics, and returns the standardized {@link ToolResult}.
|
|
@@ -24,6 +24,7 @@ import { extractSymbols } from '../utils/symbolExtractor.js';
|
|
|
24
24
|
import { getMetricCollector } from './metrics.js';
|
|
25
25
|
import { getCurrentAgentId } from '../utils/asyncContext.js';
|
|
26
26
|
import { recordFileRead, hasAgentReadFile } from '../utils/fileReadGuard.js';
|
|
27
|
+
import { runFuzzProbe, checkHeapDelta as runCheckHeapDelta, checkBehavioralDrift as runCheckBehavioralDrift, } from '../utils/analysisRunner.js';
|
|
27
28
|
import ignore from 'ignore';
|
|
28
29
|
const execAsync = promisify(exec);
|
|
29
30
|
// ─── Tool Declarations for Gemini Function Calling ───────────────────
|
|
@@ -341,6 +342,109 @@ export const toolDeclarations = [
|
|
|
341
342
|
required: ['summary'],
|
|
342
343
|
},
|
|
343
344
|
},
|
|
345
|
+
{
|
|
346
|
+
name: 'run_fuzz_probe',
|
|
347
|
+
description: 'Execute a fuzz testing probe script to detect edge-case failures, unhandled exceptions, or unexpected crashes under randomized or boundary inputs.',
|
|
348
|
+
parameters: {
|
|
349
|
+
type: SchemaType.OBJECT,
|
|
350
|
+
properties: {
|
|
351
|
+
code: {
|
|
352
|
+
type: SchemaType.STRING,
|
|
353
|
+
description: 'The fuzz test script or probe code to execute.',
|
|
354
|
+
},
|
|
355
|
+
language: {
|
|
356
|
+
type: SchemaType.STRING,
|
|
357
|
+
description: 'Runtime environment: "node", "ts-node", "python", "bash", "go", or "rust". Defaults to "node".',
|
|
358
|
+
},
|
|
359
|
+
iterations: {
|
|
360
|
+
type: SchemaType.NUMBER,
|
|
361
|
+
description: 'Optional. Number of fuzz test iterations to run.',
|
|
362
|
+
},
|
|
363
|
+
maxInputLength: {
|
|
364
|
+
type: SchemaType.NUMBER,
|
|
365
|
+
description: 'Optional. Maximum length/size for generated fuzz input vectors.',
|
|
366
|
+
},
|
|
367
|
+
seed: {
|
|
368
|
+
type: SchemaType.NUMBER,
|
|
369
|
+
description: 'Optional. Random seed for deterministic fuzz generation.',
|
|
370
|
+
},
|
|
371
|
+
timeoutMs: {
|
|
372
|
+
type: SchemaType.NUMBER,
|
|
373
|
+
description: 'Optional. Execution timeout in milliseconds.',
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
required: ['code'],
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: 'check_heap_delta',
|
|
381
|
+
description: 'Execute a heap memory inspection script to measure memory consumption, detect heap growth/leaks, and evaluate memory delta statistics.',
|
|
382
|
+
parameters: {
|
|
383
|
+
type: SchemaType.OBJECT,
|
|
384
|
+
properties: {
|
|
385
|
+
code: {
|
|
386
|
+
type: SchemaType.STRING,
|
|
387
|
+
description: 'The script or code snippet to execute for heap memory analysis.',
|
|
388
|
+
},
|
|
389
|
+
language: {
|
|
390
|
+
type: SchemaType.STRING,
|
|
391
|
+
description: 'Runtime environment: "node", "ts-node", "python", "bash", "go", or "rust". Defaults to "node".',
|
|
392
|
+
},
|
|
393
|
+
warmupIterations: {
|
|
394
|
+
type: SchemaType.NUMBER,
|
|
395
|
+
description: 'Optional. Number of warmup iterations prior to initial heap snapshot.',
|
|
396
|
+
},
|
|
397
|
+
testIterations: {
|
|
398
|
+
type: SchemaType.NUMBER,
|
|
399
|
+
description: 'Optional. Number of test iterations executed between snapshots.',
|
|
400
|
+
},
|
|
401
|
+
samplingIntervalMs: {
|
|
402
|
+
type: SchemaType.NUMBER,
|
|
403
|
+
description: 'Optional. Memory sampling interval in milliseconds.',
|
|
404
|
+
},
|
|
405
|
+
timeoutMs: {
|
|
406
|
+
type: SchemaType.NUMBER,
|
|
407
|
+
description: 'Optional. Execution timeout in milliseconds.',
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
required: ['code'],
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: 'check_behavioral_drift',
|
|
415
|
+
description: 'Execute baseline and candidate scripts in parallel or sequence to detect output differences, return value mismatches, or behavioral drift.',
|
|
416
|
+
parameters: {
|
|
417
|
+
type: SchemaType.OBJECT,
|
|
418
|
+
properties: {
|
|
419
|
+
baselineCode: {
|
|
420
|
+
type: SchemaType.STRING,
|
|
421
|
+
description: 'The baseline implementation script source code.',
|
|
422
|
+
},
|
|
423
|
+
candidateCode: {
|
|
424
|
+
type: SchemaType.STRING,
|
|
425
|
+
description: 'The candidate implementation script source code to compare against baseline.',
|
|
426
|
+
},
|
|
427
|
+
language: {
|
|
428
|
+
type: SchemaType.STRING,
|
|
429
|
+
description: 'Runtime environment: "node", "ts-node", "python", "bash", "go", or "rust". Defaults to "node".',
|
|
430
|
+
},
|
|
431
|
+
tolerance: {
|
|
432
|
+
type: SchemaType.NUMBER,
|
|
433
|
+
description: 'Optional. Numerical tolerance threshold for floating point comparison.',
|
|
434
|
+
},
|
|
435
|
+
compareKeys: {
|
|
436
|
+
type: SchemaType.ARRAY,
|
|
437
|
+
items: { type: SchemaType.STRING },
|
|
438
|
+
description: 'Optional. Specific JSON object keys/fields to compare for drift.',
|
|
439
|
+
},
|
|
440
|
+
timeoutMs: {
|
|
441
|
+
type: SchemaType.NUMBER,
|
|
442
|
+
description: 'Optional. Execution timeout in milliseconds.',
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
required: ['baselineCode', 'candidateCode'],
|
|
446
|
+
},
|
|
447
|
+
},
|
|
344
448
|
];
|
|
345
449
|
let currentApprovalMode = 'ask';
|
|
346
450
|
/**
|
|
@@ -1312,6 +1416,112 @@ export async function runDebugScript(workspaceRoot, language, code, abortSignal)
|
|
|
1312
1416
|
}
|
|
1313
1417
|
}
|
|
1314
1418
|
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Executes a fuzz probing analysis script and formats the resulting pass/fail and crash statistics.
|
|
1421
|
+
*/
|
|
1422
|
+
export async function executeFuzzProbe(workspaceRoot, code, language = 'node', config, abortSignal) {
|
|
1423
|
+
try {
|
|
1424
|
+
const res = await runFuzzProbe(workspaceRoot, code, language, {
|
|
1425
|
+
...config,
|
|
1426
|
+
abortSignal,
|
|
1427
|
+
});
|
|
1428
|
+
let output = `Fuzz Probe Result: ${res.passed ? 'PASSED' : 'FAILED'}\n`;
|
|
1429
|
+
if (res.totalFuzzRuns > 0)
|
|
1430
|
+
output += `Total Fuzz Runs: ${res.totalFuzzRuns}\n`;
|
|
1431
|
+
if (res.seed !== undefined)
|
|
1432
|
+
output += `Seed: ${res.seed}\n`;
|
|
1433
|
+
if (res.failingInputs && res.failingInputs.length > 0) {
|
|
1434
|
+
output += `Failing Inputs / Counterexamples:\n${res.failingInputs.map((inp) => ` - ${inp}`).join('\n')}\n`;
|
|
1435
|
+
}
|
|
1436
|
+
if (res.errorSummary) {
|
|
1437
|
+
output += `Error Summary:\n${res.errorSummary}\n`;
|
|
1438
|
+
}
|
|
1439
|
+
if (res.stdout)
|
|
1440
|
+
output += `\n[STDOUT]\n${res.stdout}\n`;
|
|
1441
|
+
if (res.stderr)
|
|
1442
|
+
output += `\n[STDERR]\n${res.stderr}\n`;
|
|
1443
|
+
return {
|
|
1444
|
+
output: output.trim(),
|
|
1445
|
+
...(res.exitCode !== 0 && !res.passed ? { error: `Fuzz probe failed with exit code ${res.exitCode}` } : {}),
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
catch (err) {
|
|
1449
|
+
return { output: '', error: `Fuzz probe error: ${err.message || String(err)}` };
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Executes a heap memory inspection script and formats memory consumption and growth delta metrics.
|
|
1454
|
+
*/
|
|
1455
|
+
export async function executeHeapDelta(workspaceRoot, code, language = 'node', config, abortSignal) {
|
|
1456
|
+
try {
|
|
1457
|
+
const res = await runCheckHeapDelta(workspaceRoot, code, language, {
|
|
1458
|
+
...config,
|
|
1459
|
+
abortSignal,
|
|
1460
|
+
});
|
|
1461
|
+
let output = `Heap Delta Analysis Result:\n`;
|
|
1462
|
+
output += `Initial Heap: ${res.initialHeapBytes} bytes\n`;
|
|
1463
|
+
output += `Final Heap: ${res.finalHeapBytes} bytes\n`;
|
|
1464
|
+
output += `Delta: ${res.deltaBytes} bytes\n`;
|
|
1465
|
+
output += `Leak Detected: ${res.leakDetected ? 'YES' : 'NO'}\n`;
|
|
1466
|
+
if (res.growthRateBytesPerIter !== undefined) {
|
|
1467
|
+
output += `Growth Rate: ${res.growthRateBytesPerIter} bytes/iter\n`;
|
|
1468
|
+
}
|
|
1469
|
+
if (res.samples && res.samples.length > 0) {
|
|
1470
|
+
output += `Samples (${res.samples.length}):\n`;
|
|
1471
|
+
for (const s of res.samples) {
|
|
1472
|
+
output += ` Iter ${s.iteration}: ${s.heapBytes} bytes\n`;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
if (res.stdout)
|
|
1476
|
+
output += `\n[STDOUT]\n${res.stdout}\n`;
|
|
1477
|
+
if (res.stderr)
|
|
1478
|
+
output += `\n[STDERR]\n${res.stderr}\n`;
|
|
1479
|
+
return {
|
|
1480
|
+
output: output.trim(),
|
|
1481
|
+
...(res.exitCode !== 0 ? { error: `Heap delta failed with exit code ${res.exitCode}` } : {}),
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
catch (err) {
|
|
1485
|
+
return { output: '', error: `Heap delta error: ${err.message || String(err)}` };
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Executes baseline and candidate scripts, returning output diffs and behavioral drift findings.
|
|
1490
|
+
*/
|
|
1491
|
+
export async function executeBehavioralDrift(workspaceRoot, baselineCode, candidateCode, language = 'node', config, abortSignal) {
|
|
1492
|
+
try {
|
|
1493
|
+
const res = await runCheckBehavioralDrift(workspaceRoot, baselineCode, candidateCode, language, {
|
|
1494
|
+
...config,
|
|
1495
|
+
abortSignal,
|
|
1496
|
+
});
|
|
1497
|
+
let output = `Behavioral Drift Analysis Result:\n`;
|
|
1498
|
+
output += `Has Drift: ${res.hasDrift ? 'YES' : 'NO'}\n`;
|
|
1499
|
+
if (res.diffSummary) {
|
|
1500
|
+
output += `Diff Summary:\n${res.diffSummary}\n`;
|
|
1501
|
+
}
|
|
1502
|
+
if (res.driftDetails && res.driftDetails.length > 0) {
|
|
1503
|
+
output += `Drift Details:\n`;
|
|
1504
|
+
for (const detail of res.driftDetails) {
|
|
1505
|
+
output += ` - ${detail.field ? `[${detail.field}] ` : ''}Baseline: ${JSON.stringify(detail.baseline)} vs Candidate: ${JSON.stringify(detail.candidate)}${detail.diff ? ` (${detail.diff})` : ''}\n`;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (res.baselineOutput)
|
|
1509
|
+
output += `\n[BASELINE OUTPUT]\n${res.baselineOutput}\n`;
|
|
1510
|
+
if (res.candidateOutput)
|
|
1511
|
+
output += `\n[CANDIDATE OUTPUT]\n${res.candidateOutput}\n`;
|
|
1512
|
+
if (res.stdout)
|
|
1513
|
+
output += `\n[STDOUT]\n${res.stdout}\n`;
|
|
1514
|
+
if (res.stderr)
|
|
1515
|
+
output += `\n[STDERR]\n${res.stderr}\n`;
|
|
1516
|
+
return {
|
|
1517
|
+
output: output.trim(),
|
|
1518
|
+
...(res.exitCode !== 0 ? { error: `Behavioral drift check failed with exit code ${res.exitCode}` } : {}),
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
catch (err) {
|
|
1522
|
+
return { output: '', error: `Behavioral drift error: ${err.message || String(err)}` };
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1315
1525
|
/**
|
|
1316
1526
|
* Resolves `@alias/` prefixed paths in tool arguments to the correct workspace root
|
|
1317
1527
|
* and relative path. Returns the effective workspaceRoot and the cleaned arguments.
|
|
@@ -1424,6 +1634,15 @@ export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
|
|
|
1424
1634
|
else if (normalizedToolName === 'finish_investigation') {
|
|
1425
1635
|
normalizedToolName = 'finish_task';
|
|
1426
1636
|
}
|
|
1637
|
+
else if (normalizedToolName === 'fuzz_probe' || normalizedToolName === 'run_fuzz') {
|
|
1638
|
+
normalizedToolName = 'run_fuzz_probe';
|
|
1639
|
+
}
|
|
1640
|
+
else if (normalizedToolName === 'heap_delta' || normalizedToolName === 'check_heap') {
|
|
1641
|
+
normalizedToolName = 'check_heap_delta';
|
|
1642
|
+
}
|
|
1643
|
+
else if (normalizedToolName === 'behavioral_drift' || normalizedToolName === 'check_drift') {
|
|
1644
|
+
normalizedToolName = 'check_behavioral_drift';
|
|
1645
|
+
}
|
|
1427
1646
|
// ─── Multi-Workspace Path Resolution ─────────────────────────────
|
|
1428
1647
|
// Intercept @alias/ prefixed paths and swap workspaceRoot + relative path
|
|
1429
1648
|
// before dispatching to the underlying tool functions (which remain unchanged).
|
|
@@ -1620,6 +1839,42 @@ export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
|
|
|
1620
1839
|
case 'find_recent_changes':
|
|
1621
1840
|
result = await findRecentChanges(effectiveRoot, resolvedArgs.dirPath, resolvedArgs.minutes, resolvedArgs.maxDepth);
|
|
1622
1841
|
break;
|
|
1842
|
+
case 'run_fuzz_probe': {
|
|
1843
|
+
const code = resolvedArgs.code;
|
|
1844
|
+
const language = resolvedArgs.language || 'node';
|
|
1845
|
+
const config = {
|
|
1846
|
+
iterations: resolvedArgs.iterations,
|
|
1847
|
+
maxInputLength: resolvedArgs.maxInputLength,
|
|
1848
|
+
seed: resolvedArgs.seed,
|
|
1849
|
+
timeoutMs: resolvedArgs.timeoutMs,
|
|
1850
|
+
};
|
|
1851
|
+
result = await executeFuzzProbe(effectiveRoot, code, language, config, abortSignal);
|
|
1852
|
+
break;
|
|
1853
|
+
}
|
|
1854
|
+
case 'check_heap_delta': {
|
|
1855
|
+
const code = resolvedArgs.code;
|
|
1856
|
+
const language = resolvedArgs.language || 'node';
|
|
1857
|
+
const config = {
|
|
1858
|
+
warmupIterations: resolvedArgs.warmupIterations,
|
|
1859
|
+
testIterations: resolvedArgs.testIterations,
|
|
1860
|
+
samplingIntervalMs: resolvedArgs.samplingIntervalMs,
|
|
1861
|
+
timeoutMs: resolvedArgs.timeoutMs,
|
|
1862
|
+
};
|
|
1863
|
+
result = await executeHeapDelta(effectiveRoot, code, language, config, abortSignal);
|
|
1864
|
+
break;
|
|
1865
|
+
}
|
|
1866
|
+
case 'check_behavioral_drift': {
|
|
1867
|
+
const baselineCode = resolvedArgs.baselineCode;
|
|
1868
|
+
const candidateCode = resolvedArgs.candidateCode;
|
|
1869
|
+
const language = resolvedArgs.language || 'node';
|
|
1870
|
+
const config = {
|
|
1871
|
+
tolerance: resolvedArgs.tolerance,
|
|
1872
|
+
compareKeys: resolvedArgs.compareKeys,
|
|
1873
|
+
timeoutMs: resolvedArgs.timeoutMs,
|
|
1874
|
+
};
|
|
1875
|
+
result = await executeBehavioralDrift(effectiveRoot, baselineCode, candidateCode, language, config, abortSignal);
|
|
1876
|
+
break;
|
|
1877
|
+
}
|
|
1623
1878
|
default:
|
|
1624
1879
|
result = { output: '', error: `Unknown tool: "${toolName}"` };
|
|
1625
1880
|
break;
|
package/dist/services/agent.js
CHANGED
|
@@ -26,7 +26,7 @@ import { markedTerminal } from 'marked-terminal';
|
|
|
26
26
|
const execAsync = promisify(exec);
|
|
27
27
|
import { debugLog, isDebugOn } from '../utils/logger.js';
|
|
28
28
|
import { ensureProjectStorage, ensureIgnored, readCache, writeCache, invalidateCacheForDependents, } from '../utils/projectStorage.js';
|
|
29
|
-
import { GEMINI_MODELS, isByokEnabled } from '../utils/config.js';
|
|
29
|
+
import { GEMINI_MODELS, isByokEnabled, TPM_COOLING_DELAYS } from '../utils/config.js';
|
|
30
30
|
import { createSharedChatSession, getGeneralChatConfig, getPlanExecutionConfig, compressTextUsingFlashLite, generateChatTitle, getGlobalActiveModel, setGlobalActiveModel, summarizeChatHistory, } from './ai.js';
|
|
31
31
|
import { getAndResetTurnUsage } from './proxyClient.js';
|
|
32
32
|
import { changeLogger } from './changeLogger.js';
|
|
@@ -317,6 +317,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
317
317
|
inputHandler.start(spinner);
|
|
318
318
|
changeLogger.startChangeSet(userInput);
|
|
319
319
|
let finalInput = userInput;
|
|
320
|
+
// Inter-turn cooling-off delay to allow API token buckets to settle before running intent routing
|
|
321
|
+
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.INTER_TURN_MS));
|
|
320
322
|
// Stage 0: Summarize chat history if length threshold is met
|
|
321
323
|
if (!cachedContextResult) {
|
|
322
324
|
const isDebug = isDebugOn();
|
|
@@ -359,6 +361,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
359
361
|
toolLogs.forEach((log) => p.log.step(log));
|
|
360
362
|
spinner.start(`🔍 Context gathered successfully.`);
|
|
361
363
|
}
|
|
364
|
+
// Post-investigation cooling-off delay to allow API Tokens-Per-Minute (TPM) sliding window to settle
|
|
365
|
+
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.POST_INVESTIGATION_MS));
|
|
362
366
|
}
|
|
363
367
|
let latestUsage = undefined;
|
|
364
368
|
// Collect any inputs that were queued while the Context Agent was investigating
|
|
@@ -481,7 +485,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
481
485
|
modifiedFiles.forEach((file) => p.log.message(` ${pc.dim('•')} ${file}`));
|
|
482
486
|
}
|
|
483
487
|
else {
|
|
484
|
-
summaryMsg =
|
|
488
|
+
summaryMsg =
|
|
489
|
+
'Generation was stopped by the user before completion. No code files were modified before stopping.';
|
|
485
490
|
p.log.warn(pc.yellow('\n⚠️ Generation stopped by user before completion (no files were modified).'));
|
|
486
491
|
}
|
|
487
492
|
chat.addTurn({
|
|
@@ -657,8 +662,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
657
662
|
const turnDuration = ((turnEndTime - turnStartTime) / 1000).toFixed(1);
|
|
658
663
|
p.log.info(`${pc.dim('Generated in')} ${pc.cyan(turnDuration + 's')}`);
|
|
659
664
|
// Check if generation was stopped by user
|
|
660
|
-
const isStopped = finalText.includes('Generation stopped') ||
|
|
661
|
-
finalText.includes('[Generation stopped');
|
|
665
|
+
const isStopped = finalText.includes('Generation stopped') || finalText.includes('[Generation stopped');
|
|
662
666
|
if (isStopped) {
|
|
663
667
|
const currentChanges = changeLogger.getCurrentChangeSet()?.changes || [];
|
|
664
668
|
const modifiedFiles = currentChanges
|
|
@@ -672,7 +676,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
672
676
|
modifiedFiles.forEach((file) => p.log.message(` ${pc.dim('•')} ${file}`));
|
|
673
677
|
}
|
|
674
678
|
else {
|
|
675
|
-
summaryMsg =
|
|
679
|
+
summaryMsg =
|
|
680
|
+
'Generation was stopped by the user before completion. No code files were modified before stopping.';
|
|
676
681
|
p.log.warn(pc.yellow('\n⚠️ Generation stopped by user before completion (no files were modified).'));
|
|
677
682
|
}
|
|
678
683
|
chat.addTurn({
|
|
@@ -25,6 +25,7 @@ import { InvestigationAgentRunner } from './investigationAgent.js';
|
|
|
25
25
|
import { readFile } from '../agent-tools.js';
|
|
26
26
|
import { buildDependencyGraph } from '../../utils/dependencyTracer.js';
|
|
27
27
|
import { debugLog } from '../../utils/logger.js';
|
|
28
|
+
import { TPM_COOLING_DELAYS } from '../../utils/config.js';
|
|
28
29
|
// ─── Constants ───────────────────────────────────────────────────────
|
|
29
30
|
/** Maximum total relevant files across all agents after merge. */
|
|
30
31
|
const MAX_TOTAL_FILES = 30;
|
|
@@ -57,6 +58,8 @@ export class InvestigationOrchestrator {
|
|
|
57
58
|
const agents = agentAssignments.map((assignment) => new InvestigationAgentRunner(assignment.agentLabel, assignment.domains, workspaceRoot, readCache, projectTree, projectType));
|
|
58
59
|
// 3. Run all agents in parallel
|
|
59
60
|
const startTime = Date.now();
|
|
61
|
+
// Cooling-off pause to prevent burst 429 errors when initiating parallel agent dispatch
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.PARALLEL_DISPATCH_MS));
|
|
60
63
|
// Limit parallel investigation agents to 2 to avoid Vertex AI RESOURCE_EXHAUSTED errors
|
|
61
64
|
const MAX_CONCURRENT = 2;
|
|
62
65
|
const results = [];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { executeTool, getToolDeclarations as getBaseToolDeclarations } from '../agent-tools.js';
|
|
2
3
|
import { MessageBus } from './messageBus.js';
|
|
3
4
|
import { debugLog } from '../../utils/logger.js';
|
|
@@ -149,6 +150,31 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
|
|
|
149
150
|
const matchCount = (outputText.match(/\.\/[^:]+:\d+:/g) || []).length;
|
|
150
151
|
resultSummary = `${matchCount} matches`;
|
|
151
152
|
}
|
|
153
|
+
else if (name === 'rename_file') {
|
|
154
|
+
const src = String(args.sourcePath ?? '');
|
|
155
|
+
const tgt = String(args.targetPath ?? '');
|
|
156
|
+
targetDesc = `${src} -> ${tgt}`;
|
|
157
|
+
actionDesc = (src && tgt && path.dirname(src) === path.dirname(tgt)) ? 'Renamed' : 'Moved';
|
|
158
|
+
}
|
|
159
|
+
else if (name === 'delete_file') {
|
|
160
|
+
targetDesc = String(args.filePath);
|
|
161
|
+
actionDesc = 'Deleted';
|
|
162
|
+
}
|
|
163
|
+
else if (name === 'run_fuzz_probe') {
|
|
164
|
+
targetDesc = String(args.language ?? 'node');
|
|
165
|
+
actionDesc = 'Ran fuzz probe';
|
|
166
|
+
resultSummary = typeof result === 'object' && result?.error ? result.error : 'Completed fuzz probe';
|
|
167
|
+
}
|
|
168
|
+
else if (name === 'check_heap_delta') {
|
|
169
|
+
targetDesc = String(args.language ?? 'node');
|
|
170
|
+
actionDesc = 'Inspected heap delta';
|
|
171
|
+
resultSummary = typeof result === 'object' && result?.error ? result.error : 'Completed heap analysis';
|
|
172
|
+
}
|
|
173
|
+
else if (name === 'check_behavioral_drift') {
|
|
174
|
+
targetDesc = String(args.language ?? 'node');
|
|
175
|
+
actionDesc = 'Checked behavioral drift';
|
|
176
|
+
resultSummary = typeof result === 'object' && result?.error ? result.error : 'Completed drift comparison';
|
|
177
|
+
}
|
|
152
178
|
return result;
|
|
153
179
|
}
|
|
154
180
|
catch (error) {
|
|
@@ -72,7 +72,7 @@ export class SubAgentRunner {
|
|
|
72
72
|
`3. EXECUTION WORKFLOW:\n` +
|
|
73
73
|
` - Step 1 (Investigate): Use 'grep_search', 'read_file', or 'list_directory' to inspect the codebase as needed.\n` +
|
|
74
74
|
` - Step 2 (Execute): Use 'modify_file' or 'write_file' to implement the required changes.\n` +
|
|
75
|
-
` - Step 3 (Verify): Use 'run_debug_script' or 'run_command' to test your changes if necessary.\n` +
|
|
75
|
+
` - Step 3 (Verify): Use 'run_debug_script', 'run_fuzz_probe', 'check_heap_delta', 'check_behavioral_drift', or 'run_command' to test your changes if necessary.\n` +
|
|
76
76
|
` - Step 4 (Conclude): Once your specific objective is fully met, stop calling tools and return a text summary of your changes.\n` +
|
|
77
77
|
`4. Coordinate: Use 'read_messages' to check for updates from other agents. Use 'post_message' if you discover breaking changes affecting others.\n` +
|
|
78
78
|
`</critical_guidelines>`);
|
|
@@ -41,7 +41,7 @@ let globalSessionAccumulatedUsage = {
|
|
|
41
41
|
totalTokenCount: 0,
|
|
42
42
|
creditsUsed: 0,
|
|
43
43
|
remainingBalance: undefined,
|
|
44
|
-
modelsUsed: {}
|
|
44
|
+
modelsUsed: {},
|
|
45
45
|
};
|
|
46
46
|
export function getAndResetTurnUsage() {
|
|
47
47
|
const current = { ...globalSessionAccumulatedUsage };
|
|
@@ -52,7 +52,7 @@ export function getAndResetTurnUsage() {
|
|
|
52
52
|
totalTokenCount: 0,
|
|
53
53
|
creditsUsed: 0,
|
|
54
54
|
remainingBalance: undefined,
|
|
55
|
-
modelsUsed: {}
|
|
55
|
+
modelsUsed: {},
|
|
56
56
|
};
|
|
57
57
|
return current;
|
|
58
58
|
}
|
|
@@ -117,6 +117,7 @@ export class ProxyClient {
|
|
|
117
117
|
if ((response.status === 429 || response.status === 503) && attempt < MAX_RETRIES) {
|
|
118
118
|
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
119
119
|
const delayTime = Math.round(exponentialDelay * (0.5 + Math.random() * 0.5));
|
|
120
|
+
process.stdout.write('\n');
|
|
120
121
|
console.warn(`Rate limit or service unavailable hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
121
122
|
await delay(delayTime, abortSignal);
|
|
122
123
|
attempt++;
|
|
@@ -198,7 +199,7 @@ export class ProxyClient {
|
|
|
198
199
|
collector?.accumulateUsage({
|
|
199
200
|
promptTokens: data.usage.promptTokens || 0,
|
|
200
201
|
candidatesTokens: data.usage.candidatesTokens || 0,
|
|
201
|
-
cachedTokens: data.usage.cachedTokens || 0
|
|
202
|
+
cachedTokens: data.usage.cachedTokens || 0,
|
|
202
203
|
});
|
|
203
204
|
globalSessionAccumulatedUsage.promptTokens += data.usage.promptTokens || 0;
|
|
204
205
|
globalSessionAccumulatedUsage.candidatesTokens += data.usage.candidatesTokens || 0;
|
|
@@ -209,7 +210,8 @@ export class ProxyClient {
|
|
|
209
210
|
if (data.usage.remainingBalance !== undefined) {
|
|
210
211
|
globalSessionAccumulatedUsage.remainingBalance = data.usage.remainingBalance;
|
|
211
212
|
}
|
|
212
|
-
globalSessionAccumulatedUsage.modelsUsed[modelName] =
|
|
213
|
+
globalSessionAccumulatedUsage.modelsUsed[modelName] =
|
|
214
|
+
(globalSessionAccumulatedUsage.modelsUsed[modelName] || 0) + 1;
|
|
213
215
|
}
|
|
214
216
|
if (data.groundingMetadata) {
|
|
215
217
|
groundingMetadata = data.groundingMetadata;
|
|
@@ -236,6 +238,7 @@ export class ProxyClient {
|
|
|
236
238
|
if (attempt < MAX_RETRIES) {
|
|
237
239
|
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
238
240
|
const delayTime = Math.round(exponentialDelay * (0.5 + Math.random() * 0.5));
|
|
241
|
+
process.stdout.write('\n');
|
|
239
242
|
console.warn(`Rate limit or service unavailable hit during stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
240
243
|
await delay(delayTime, abortSignal);
|
|
241
244
|
attempt++;
|