minovative-mind-cli 1.5.1 → 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.
- package/README.md +59 -45
- package/dist/commands/chat.js +10 -2
- package/dist/services/agent/slashCommands.js +369 -42
- package/dist/services/agent/toolLoop.d.ts +1 -1
- package/dist/services/agent/toolLoop.js +7 -2
- package/dist/services/agent/types.d.ts +2 -0
- package/dist/services/agent-tools.d.ts +9 -4
- package/dist/services/agent-tools.js +272 -34
- package/dist/services/agent.d.ts +8 -0
- package/dist/services/agent.js +288 -40
- package/dist/services/ai.d.ts +19 -5
- package/dist/services/ai.js +182 -36
- package/dist/services/changeLogger.d.ts +142 -0
- package/dist/services/changeLogger.js +132 -3
- package/dist/services/contextAgent.d.ts +6 -1
- package/dist/services/contextAgent.js +112 -19
- package/dist/services/embeddingIndex.d.ts +82 -0
- package/dist/services/embeddingIndex.js +613 -0
- package/dist/services/investigationComplexity.d.ts +45 -0
- package/dist/services/investigationComplexity.js +91 -0
- package/dist/services/metrics.d.ts +18 -0
- package/dist/services/metrics.js +7 -0
- package/dist/services/orchestration/fileLockRegistry.d.ts +125 -0
- package/dist/services/orchestration/fileLockRegistry.js +276 -0
- package/dist/services/orchestration/investigationAgent.d.ts +85 -0
- package/dist/services/orchestration/investigationAgent.js +362 -0
- package/dist/services/orchestration/investigationOrchestrator.d.ts +53 -0
- package/dist/services/orchestration/investigationOrchestrator.js +180 -0
- package/dist/services/orchestration/messageBus.d.ts +162 -0
- package/dist/services/orchestration/messageBus.js +225 -0
- package/dist/services/orchestration/orchestrator.d.ts +45 -0
- package/dist/services/orchestration/orchestrator.js +217 -0
- package/dist/services/orchestration/readCache.d.ts +79 -0
- package/dist/services/orchestration/readCache.js +108 -0
- package/dist/services/orchestration/scopedTools.d.ts +57 -0
- package/dist/services/orchestration/scopedTools.js +172 -0
- package/dist/services/orchestration/subAgent.d.ts +58 -0
- package/dist/services/orchestration/subAgent.js +190 -0
- package/dist/services/orchestration/taskGraph.d.ts +129 -0
- package/dist/services/orchestration/taskGraph.js +254 -0
- package/dist/services/proxyClient.d.ts +25 -0
- package/dist/services/proxyClient.js +60 -0
- package/dist/services/workspaceRegistry.d.ts +137 -0
- package/dist/services/workspaceRegistry.js +270 -0
- package/dist/utils/asyncContext.d.ts +16 -0
- package/dist/utils/asyncContext.js +25 -0
- package/dist/utils/config.d.ts +3 -1
- package/dist/utils/config.js +3 -1
- package/dist/utils/contextPrompts.js +10 -3
- package/dist/utils/dependencyTracer/modules/api.d.ts +9 -0
- package/dist/utils/dependencyTracer/modules/api.js +62 -0
- package/dist/utils/dependencyTracer/modules/graph.d.ts +9 -0
- package/dist/utils/dependencyTracer/modules/graph.js +23 -0
- package/dist/utils/dependencyTracer/modules/profiles.d.ts +7 -0
- package/dist/utils/dependencyTracer/modules/profiles.js +120 -0
- package/dist/utils/dependencyTracer/modules/resolver.d.ts +7 -0
- package/dist/utils/dependencyTracer/modules/resolver.js +51 -0
- package/dist/utils/dependencyTracer/modules/types.d.ts +4 -0
- package/dist/utils/dependencyTracer/modules/types.js +1 -0
- package/dist/utils/dependencyTracer/modules/walker.d.ts +1 -0
- package/dist/utils/dependencyTracer/modules/walker.js +48 -0
- package/dist/utils/dependencyTracer.js +31 -17
- package/dist/utils/excludedExtensions.js +0 -1
- package/dist/utils/historyPrompt.d.ts +9 -0
- package/dist/utils/historyPrompt.js +87 -0
- package/dist/utils/logo.js +7 -7
- package/dist/utils/paste.d.ts +21 -0
- package/dist/utils/paste.js +22 -1
- package/dist/utils/pathSecurity.d.ts +31 -0
- package/dist/utils/pathSecurity.js +48 -0
- package/dist/utils/profiles.d.ts +2 -0
- package/dist/utils/profiles.js +44 -0
- package/dist/utils/projectStorage.js +10 -7
- package/dist/utils/systemPrompts.d.ts +6 -3
- package/dist/utils/systemPrompts.js +111 -6
- package/dist/utils/types.d.ts +33 -0
- package/dist/utils/types.js +1 -0
- package/oclif.manifest.json +2 -2
- package/package.json +4 -3
|
@@ -4,7 +4,8 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { SchemaType } from '@google/generative-ai';
|
|
7
|
-
import { resolveAndValidatePath } from '../utils/pathSecurity.js';
|
|
7
|
+
import { resolveAndValidatePath, resolveAndValidateMultiWorkspacePath } from '../utils/pathSecurity.js';
|
|
8
|
+
import { workspaceRegistry } from './workspaceRegistry.js';
|
|
8
9
|
import { changeLogger } from './changeLogger.js';
|
|
9
10
|
import { findBestMatch, applyMatch } from '../utils/fuzzyMatch.js';
|
|
10
11
|
import { validateSyntax } from '../utils/syntaxValidator.js';
|
|
@@ -13,8 +14,22 @@ import { findDependencies, formatDependencyResult } from '../utils/dependencyTra
|
|
|
13
14
|
import { atomicWriteFile } from '../utils/atomicWrite.js';
|
|
14
15
|
import { EXCLUDED_EXTENSIONS } from '../utils/excludedExtensions.js';
|
|
15
16
|
import { extractSymbols } from '../utils/symbolExtractor.js';
|
|
17
|
+
import { getMetricCollector } from './metrics.js';
|
|
16
18
|
const execAsync = promisify(exec);
|
|
17
19
|
// ─── Tool Declarations for Gemini Function Calling ───────────────────
|
|
20
|
+
let _semanticSearchEnabled = true;
|
|
21
|
+
export function isSemanticSearchEnabled() {
|
|
22
|
+
return _semanticSearchEnabled;
|
|
23
|
+
}
|
|
24
|
+
export function setSemanticSearchEnabled(val) {
|
|
25
|
+
_semanticSearchEnabled = val;
|
|
26
|
+
}
|
|
27
|
+
export function getToolDeclarations() {
|
|
28
|
+
if (_semanticSearchEnabled) {
|
|
29
|
+
return toolDeclarations;
|
|
30
|
+
}
|
|
31
|
+
return toolDeclarations.filter((t) => t.name !== 'semantic_search');
|
|
32
|
+
}
|
|
18
33
|
/**
|
|
19
34
|
* FunctionDeclaration-compatible schema objects that describe
|
|
20
35
|
* every tool the agent can invoke. Passed to the model at init.
|
|
@@ -22,13 +37,13 @@ const execAsync = promisify(exec);
|
|
|
22
37
|
export const toolDeclarations = [
|
|
23
38
|
{
|
|
24
39
|
name: 'read_file',
|
|
25
|
-
description: 'Read the contents of a file at the given path relative to the workspace root.
|
|
40
|
+
description: 'Read the contents of a file at the given path relative to the workspace root. Supports text files and native parsing of .pdf files (including math and diagrams). Use startLine and endLine to read specific chunks of massive files to avoid context limits. For files in external workspaces, prefix the path with @alias/ (e.g., @backend/src/routes.ts).',
|
|
26
41
|
parameters: {
|
|
27
42
|
type: SchemaType.OBJECT,
|
|
28
43
|
properties: {
|
|
29
44
|
filePath: {
|
|
30
45
|
type: SchemaType.STRING,
|
|
31
|
-
description: 'Relative path to the file from the workspace root.',
|
|
46
|
+
description: 'Relative path to the file from the workspace root, or @alias/path for external workspaces.',
|
|
32
47
|
},
|
|
33
48
|
startLine: {
|
|
34
49
|
type: SchemaType.NUMBER,
|
|
@@ -49,13 +64,13 @@ export const toolDeclarations = [
|
|
|
49
64
|
},
|
|
50
65
|
{
|
|
51
66
|
name: 'write_file',
|
|
52
|
-
description: 'Create a new file or completely overwrite an existing file with the provided content. Use modify_file for targeted edits instead.',
|
|
67
|
+
description: 'Create a new file or completely overwrite an existing file with the provided content. Use modify_file for targeted edits instead. For files in external workspaces, prefix the path with @alias/ (e.g., @backend/src/routes.ts).',
|
|
53
68
|
parameters: {
|
|
54
69
|
type: SchemaType.OBJECT,
|
|
55
70
|
properties: {
|
|
56
71
|
filePath: {
|
|
57
72
|
type: SchemaType.STRING,
|
|
58
|
-
description: 'Relative path to the file from the workspace root.',
|
|
73
|
+
description: 'Relative path to the file from the workspace root, or @alias/path for external workspaces.',
|
|
59
74
|
},
|
|
60
75
|
content: {
|
|
61
76
|
type: SchemaType.STRING,
|
|
@@ -67,13 +82,13 @@ export const toolDeclarations = [
|
|
|
67
82
|
},
|
|
68
83
|
{
|
|
69
84
|
name: 'delete_file',
|
|
70
|
-
description: 'Deletes a file from the filesystem. Use this instead of running an rm command.',
|
|
85
|
+
description: 'Deletes a file from the filesystem. Use this instead of running an rm command. For files in external workspaces, prefix the path with @alias/ (e.g., @backend/src/routes.ts).',
|
|
71
86
|
parameters: {
|
|
72
87
|
type: SchemaType.OBJECT,
|
|
73
88
|
properties: {
|
|
74
89
|
filePath: {
|
|
75
90
|
type: SchemaType.STRING,
|
|
76
|
-
description: 'Relative path to the file to delete.',
|
|
91
|
+
description: 'Relative path to the file to delete, or @alias/path for external workspaces.',
|
|
77
92
|
},
|
|
78
93
|
},
|
|
79
94
|
required: ['filePath'],
|
|
@@ -81,17 +96,17 @@ export const toolDeclarations = [
|
|
|
81
96
|
},
|
|
82
97
|
{
|
|
83
98
|
name: 'rename_file',
|
|
84
|
-
description: 'Moves or renames a file. Use this instead of running an mv command.',
|
|
99
|
+
description: 'Moves or renames a file. Use this instead of running an mv command. For files in external workspaces, prefix the path with @alias/ (e.g., @backend/src/routes.ts).',
|
|
85
100
|
parameters: {
|
|
86
101
|
type: SchemaType.OBJECT,
|
|
87
102
|
properties: {
|
|
88
103
|
sourcePath: {
|
|
89
104
|
type: SchemaType.STRING,
|
|
90
|
-
description: 'Relative path to the file to move/rename.',
|
|
105
|
+
description: 'Relative path to the file to move/rename, or @alias/path for external workspaces.',
|
|
91
106
|
},
|
|
92
107
|
targetPath: {
|
|
93
108
|
type: SchemaType.STRING,
|
|
94
|
-
description: 'New relative path for the file.',
|
|
109
|
+
description: 'New relative path for the file, or @alias/path for external workspaces.',
|
|
95
110
|
},
|
|
96
111
|
},
|
|
97
112
|
required: ['sourcePath', 'targetPath'],
|
|
@@ -99,13 +114,13 @@ export const toolDeclarations = [
|
|
|
99
114
|
},
|
|
100
115
|
{
|
|
101
116
|
name: 'modify_file',
|
|
102
|
-
description: 'Perform one or multiple targeted search-and-replace edits in a single file. The search strings must match exactly (including whitespace). This is preferred over write_file for editing existing files
|
|
117
|
+
description: 'Perform one or multiple targeted search-and-replace edits in a single file. CRITICAL REQUIREMENT: You MUST use read_file or grep_search to fetch the exact current file content BEFORE using this tool. Do NOT guess or hallucinate the searchContent without reading the exact lines first, or the edit will fail. The search strings must match the current file exactly (including whitespace). This is preferred over write_file for editing existing files. For files in external workspaces, prefix the path with @alias/ (e.g., @backend/src/routes.ts).',
|
|
103
118
|
parameters: {
|
|
104
119
|
type: SchemaType.OBJECT,
|
|
105
120
|
properties: {
|
|
106
121
|
filePath: {
|
|
107
122
|
type: SchemaType.STRING,
|
|
108
|
-
description: 'Relative path to the file from the workspace root.',
|
|
123
|
+
description: 'Relative path to the file from the workspace root, or @alias/path for external workspaces.',
|
|
109
124
|
},
|
|
110
125
|
edits: {
|
|
111
126
|
type: SchemaType.ARRAY,
|
|
@@ -131,13 +146,13 @@ export const toolDeclarations = [
|
|
|
131
146
|
},
|
|
132
147
|
{
|
|
133
148
|
name: 'list_directory',
|
|
134
|
-
description: 'List files and subdirectories in a directory relative to the workspace root. Returns a recursive tree structure.',
|
|
149
|
+
description: 'List files and subdirectories in a directory relative to the workspace root. Returns a recursive tree structure. For directories in external workspaces, prefix the path with @alias/ (e.g., @backend/src). Use "@all" to see a summary of all registered workspaces.',
|
|
135
150
|
parameters: {
|
|
136
151
|
type: SchemaType.OBJECT,
|
|
137
152
|
properties: {
|
|
138
153
|
dirPath: {
|
|
139
154
|
type: SchemaType.STRING,
|
|
140
|
-
description: 'Relative path to the directory from the workspace root
|
|
155
|
+
description: 'Relative path to the directory from the workspace root, @alias/path for external workspaces, or "@all" for all workspaces.',
|
|
141
156
|
},
|
|
142
157
|
maxDepth: {
|
|
143
158
|
type: SchemaType.NUMBER,
|
|
@@ -163,7 +178,7 @@ export const toolDeclarations = [
|
|
|
163
178
|
},
|
|
164
179
|
{
|
|
165
180
|
name: 'grep_search',
|
|
166
|
-
description: 'Search for a text pattern across files in the workspace. Returns matching file paths with line numbers and content snippets. Uses Extended Regular Expressions (grep -E). IMPORTANT: grep searches line-by-line. Do NOT search for long lists of Tailwind classes or multi-line strings, as they will fail if line-wrapped. Search for short, unique substrings.',
|
|
181
|
+
description: 'Search for a text pattern across files in the workspace. Returns matching file paths with line numbers and content snippets. Uses Extended Regular Expressions (grep -E). IMPORTANT: grep searches line-by-line. Do NOT search for long lists of Tailwind classes or multi-line strings, as they will fail if line-wrapped. Search for short, unique substrings. Use the workspace parameter to search external workspaces.',
|
|
167
182
|
parameters: {
|
|
168
183
|
type: SchemaType.OBJECT,
|
|
169
184
|
properties: {
|
|
@@ -175,6 +190,10 @@ export const toolDeclarations = [
|
|
|
175
190
|
type: SchemaType.STRING,
|
|
176
191
|
description: 'Optional glob to restrict file types, e.g. "*.ts" or "*.py". Defaults to all files.',
|
|
177
192
|
},
|
|
193
|
+
workspace: {
|
|
194
|
+
type: SchemaType.STRING,
|
|
195
|
+
description: 'Optional. Workspace alias to search in (e.g., "backend"), or "all" to search all registered workspaces. Defaults to the primary workspace.',
|
|
196
|
+
},
|
|
178
197
|
},
|
|
179
198
|
required: ['pattern'],
|
|
180
199
|
},
|
|
@@ -240,6 +259,24 @@ export const toolDeclarations = [
|
|
|
240
259
|
required: ['language', 'code'],
|
|
241
260
|
},
|
|
242
261
|
},
|
|
262
|
+
{
|
|
263
|
+
name: 'semantic_search',
|
|
264
|
+
description: 'Search the codebase by meaning and concept rather than exact text match. Use this when you need to find code related to a concept, pattern, or behavior but don\'t know the exact variable or function names to grep for. Examples: "error handling for API requests", "user authentication flow", "database connection pooling logic". Returns ranked results with file paths, line ranges, and similarity scores.',
|
|
265
|
+
parameters: {
|
|
266
|
+
type: SchemaType.OBJECT,
|
|
267
|
+
properties: {
|
|
268
|
+
query: {
|
|
269
|
+
type: SchemaType.STRING,
|
|
270
|
+
description: "Natural language description of what you're looking for in the codebase.",
|
|
271
|
+
},
|
|
272
|
+
topK: {
|
|
273
|
+
type: SchemaType.NUMBER,
|
|
274
|
+
description: 'Number of results to return. Defaults to 5, maximum 15.',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
required: ['query'],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
243
280
|
];
|
|
244
281
|
let currentApprovalMode = 'ask';
|
|
245
282
|
export function getApprovalMode() {
|
|
@@ -248,6 +285,13 @@ export function getApprovalMode() {
|
|
|
248
285
|
export function setApprovalMode(mode) {
|
|
249
286
|
currentApprovalMode = mode;
|
|
250
287
|
}
|
|
288
|
+
let subAgentsEnabled = true;
|
|
289
|
+
export function isSubAgentsEnabled() {
|
|
290
|
+
return subAgentsEnabled;
|
|
291
|
+
}
|
|
292
|
+
export function setSubAgentsEnabled(enabled) {
|
|
293
|
+
subAgentsEnabled = enabled;
|
|
294
|
+
}
|
|
251
295
|
/**
|
|
252
296
|
* If set to 'skip-once', reverts to 'ask' after a single command is run.
|
|
253
297
|
*/
|
|
@@ -300,6 +344,23 @@ async function getIgnoredPaths(workspaceRoot) {
|
|
|
300
344
|
export async function readFile(workspaceRoot, filePath, startLine, endLine, targetElements) {
|
|
301
345
|
try {
|
|
302
346
|
const absPath = resolveAndValidatePath(workspaceRoot, filePath);
|
|
347
|
+
// Handle PDF files
|
|
348
|
+
if (filePath.toLowerCase().endsWith('.pdf')) {
|
|
349
|
+
const stats = await fs.stat(absPath);
|
|
350
|
+
const fileSizeMB = stats.size / (1024 * 1024);
|
|
351
|
+
if (fileSizeMB > 10) {
|
|
352
|
+
return {
|
|
353
|
+
output: '',
|
|
354
|
+
error: `File exceeds the 10MB safety limit to prevent excessive credit consumption. Please split the PDF into smaller chunks. (Size: ${fileSizeMB.toFixed(2)}MB)`,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
const buffer = await fs.readFile(absPath);
|
|
358
|
+
const base64Data = buffer.toString('base64');
|
|
359
|
+
return {
|
|
360
|
+
output: `[PDF Data Attached: ${filePath}]`,
|
|
361
|
+
inlineData: { mimeType: 'application/pdf', data: base64Data },
|
|
362
|
+
};
|
|
363
|
+
}
|
|
303
364
|
let content = await fs.readFile(absPath, 'utf-8');
|
|
304
365
|
if (targetElements && targetElements.length > 0) {
|
|
305
366
|
content = extractSymbols(content, filePath, targetElements);
|
|
@@ -356,6 +417,9 @@ export async function writeFile(workspaceRoot, filePath, content) {
|
|
|
356
417
|
}
|
|
357
418
|
catch (err) {
|
|
358
419
|
const message = err instanceof Error ? err.message : String(err);
|
|
420
|
+
const collector = getMetricCollector();
|
|
421
|
+
if (collector)
|
|
422
|
+
collector.recordWriteFailure();
|
|
359
423
|
return {
|
|
360
424
|
output: '',
|
|
361
425
|
error: `Failed to write file "${filePath}": ${message}`,
|
|
@@ -417,6 +481,9 @@ export async function modifyFile(workspaceRoot, filePath, edits) {
|
|
|
417
481
|
const edit = edits[i];
|
|
418
482
|
const match = findBestMatch(modified, edit.searchContent);
|
|
419
483
|
if (!match) {
|
|
484
|
+
const collector = getMetricCollector();
|
|
485
|
+
if (collector)
|
|
486
|
+
collector.recordModifyFailure();
|
|
420
487
|
if (attempt < MAX_MODIFY_RETRIES) {
|
|
421
488
|
// Break out of inner loop, triggering a retry in outer loop
|
|
422
489
|
modified = existing; // reset
|
|
@@ -431,6 +498,18 @@ export async function modifyFile(workspaceRoot, filePath, edits) {
|
|
|
431
498
|
}
|
|
432
499
|
modified = applyMatch(modified, match, edit.replaceContent);
|
|
433
500
|
strategies.push(`Edit #${i + 1}: ${match.strategy}`);
|
|
501
|
+
const collector = getMetricCollector();
|
|
502
|
+
if (collector) {
|
|
503
|
+
if (match.strategy.startsWith('Exact Match')) {
|
|
504
|
+
collector.recordMatchTier('exact');
|
|
505
|
+
}
|
|
506
|
+
else if (match.strategy.startsWith('Levenshtein')) {
|
|
507
|
+
collector.recordMatchTier('levenshtein');
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
collector.recordMatchTier('normalized');
|
|
511
|
+
}
|
|
512
|
+
}
|
|
434
513
|
}
|
|
435
514
|
// If we broke out early for a retry, the modified string will equal the existing string
|
|
436
515
|
// (or we haven't completed all edits), so we continue to the next attempt.
|
|
@@ -528,7 +607,9 @@ export async function runCommand(workspaceRoot, command, abortSignal) {
|
|
|
528
607
|
// Truncate command output to prevent memory blowout from massive build logs
|
|
529
608
|
const MAX_CMD_OUTPUT = 15_000;
|
|
530
609
|
if (output.length > MAX_CMD_OUTPUT) {
|
|
531
|
-
output =
|
|
610
|
+
output =
|
|
611
|
+
output.substring(0, MAX_CMD_OUTPUT) +
|
|
612
|
+
`\n\n... (Output truncated: ${output.length} bytes exceeded 15KB limit. To view the rest, pipe the command to a file and read it in chunks, or use grep.)`;
|
|
532
613
|
}
|
|
533
614
|
return { output: output || '(command produced no output)' };
|
|
534
615
|
}
|
|
@@ -537,7 +618,8 @@ export async function runCommand(workspaceRoot, command, abortSignal) {
|
|
|
537
618
|
// Also truncate error output
|
|
538
619
|
const MAX_ERR_OUTPUT = 15_000;
|
|
539
620
|
const truncatedMsg = message.length > MAX_ERR_OUTPUT
|
|
540
|
-
? message.substring(0, MAX_ERR_OUTPUT) +
|
|
621
|
+
? message.substring(0, MAX_ERR_OUTPUT) +
|
|
622
|
+
`\n\n... (Error output truncated: ${message.length} bytes exceeded 15KB limit. Pipe to a file if you need full logs.)`
|
|
541
623
|
: message;
|
|
542
624
|
return { output: '', error: `Command failed: ${truncatedMsg}` };
|
|
543
625
|
}
|
|
@@ -574,7 +656,9 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob, abortSignal)
|
|
|
574
656
|
}
|
|
575
657
|
// Node.js fallback for Windows
|
|
576
658
|
const regexPattern = new RegExp(pattern, 'gi'); // Emulate grep -i and global match
|
|
577
|
-
const globPattern = fileGlob
|
|
659
|
+
const globPattern = fileGlob
|
|
660
|
+
? new RegExp('^' + fileGlob.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$', 'i')
|
|
661
|
+
: null;
|
|
578
662
|
const results = [];
|
|
579
663
|
async function walk(dir) {
|
|
580
664
|
if (results.length >= 50)
|
|
@@ -811,34 +895,188 @@ export async function runDebugScript(workspaceRoot, language, code, abortSignal)
|
|
|
811
895
|
}
|
|
812
896
|
}
|
|
813
897
|
/**
|
|
814
|
-
*
|
|
815
|
-
*
|
|
898
|
+
* Resolves `@alias/` prefixed paths in tool arguments to the correct workspace root
|
|
899
|
+
* and relative path. Returns the effective workspaceRoot and the cleaned arguments.
|
|
900
|
+
*/
|
|
901
|
+
function resolveWorkspaceArgs(primaryRoot, toolName, args) {
|
|
902
|
+
// Only resolve if workspaces are registered
|
|
903
|
+
if (!workspaceRegistry.hasWorkspaces()) {
|
|
904
|
+
return { effectiveRoot: primaryRoot, resolvedArgs: args };
|
|
905
|
+
}
|
|
906
|
+
const resolvedArgs = { ...args };
|
|
907
|
+
// Map of tool-name -> argument keys that hold file/dir paths
|
|
908
|
+
const pathArgKeys = {
|
|
909
|
+
read_file: ['filePath'],
|
|
910
|
+
write_file: ['filePath'],
|
|
911
|
+
delete_file: ['filePath'],
|
|
912
|
+
modify_file: ['filePath'],
|
|
913
|
+
list_directory: ['dirPath'],
|
|
914
|
+
find_dependencies: ['filePath'],
|
|
915
|
+
find_recent_changes: ['dirPath'],
|
|
916
|
+
rename_file: ['sourcePath', 'targetPath'],
|
|
917
|
+
};
|
|
918
|
+
const keys = pathArgKeys[toolName];
|
|
919
|
+
if (!keys) {
|
|
920
|
+
return { effectiveRoot: primaryRoot, resolvedArgs };
|
|
921
|
+
}
|
|
922
|
+
let effectiveRoot = primaryRoot;
|
|
923
|
+
for (const key of keys) {
|
|
924
|
+
const val = resolvedArgs[key];
|
|
925
|
+
if (typeof val !== 'string' || !val.startsWith('@'))
|
|
926
|
+
continue;
|
|
927
|
+
// Special case: "@all" for list_directory — handled separately in executeTool
|
|
928
|
+
if (val === '@all')
|
|
929
|
+
continue;
|
|
930
|
+
const resolved = resolveAndValidateMultiWorkspacePath(primaryRoot, val);
|
|
931
|
+
effectiveRoot = resolved.workspaceRoot;
|
|
932
|
+
resolvedArgs[key] = resolved.relativePath;
|
|
933
|
+
}
|
|
934
|
+
return { effectiveRoot, resolvedArgs };
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Runs grep across all registered workspaces (primary + external) and merges results.
|
|
938
|
+
* Results from external workspaces are prefixed with @alias/ for disambiguation.
|
|
816
939
|
*/
|
|
940
|
+
async function crossWorkspaceGrep(primaryRoot, pattern, fileGlob, abortSignal) {
|
|
941
|
+
const allRoots = workspaceRegistry.getAllRoots(primaryRoot);
|
|
942
|
+
const allResults = [];
|
|
943
|
+
for (const { alias, root } of allRoots) {
|
|
944
|
+
const result = await grepSearch(root, pattern, fileGlob, abortSignal);
|
|
945
|
+
if (result.error)
|
|
946
|
+
continue;
|
|
947
|
+
// Extract the raw text from the XML wrapper
|
|
948
|
+
const cdataMatch = result.output.match(/<!\[CDATA\[\n([\s\S]*?)\n\]\]>/);
|
|
949
|
+
const rawText = cdataMatch ? cdataMatch[1] : result.output;
|
|
950
|
+
if (rawText.startsWith('No matches found'))
|
|
951
|
+
continue;
|
|
952
|
+
// Prefix each result line with the workspace alias for disambiguation
|
|
953
|
+
const prefix = alias ? `@${alias}/` : '';
|
|
954
|
+
const lines = rawText.split('\n').filter((l) => l.trim());
|
|
955
|
+
for (const line of lines) {
|
|
956
|
+
if (line.startsWith('...')) {
|
|
957
|
+
allResults.push(line);
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
// Lines are in format "./path:lineNum:content" — prepend alias prefix
|
|
961
|
+
allResults.push(prefix ? line.replace(/^\.?\//, `@${alias}/`) : line);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (allResults.length === 0) {
|
|
966
|
+
return { output: `No matches found for "${pattern}" across all workspaces.` };
|
|
967
|
+
}
|
|
968
|
+
const limited = allResults.slice(0, 80);
|
|
969
|
+
const resultText = limited.join('\n') +
|
|
970
|
+
(allResults.length > 80 ? `\n\n... (${allResults.length - 80} more results truncated)` : '');
|
|
971
|
+
const wrappedResult = `<workspace_file path="grep_search_results">\n<content_data><![CDATA[\n${sanitizeForCDATA(resultText)}\n]]></content_data>\n</workspace_file>`;
|
|
972
|
+
return { output: wrappedResult };
|
|
973
|
+
}
|
|
817
974
|
export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
|
|
975
|
+
// ─── Multi-Workspace Path Resolution ─────────────────────────────
|
|
976
|
+
// Intercept @alias/ prefixed paths and swap workspaceRoot + relative path
|
|
977
|
+
// before dispatching to the underlying tool functions (which remain unchanged).
|
|
978
|
+
const { effectiveRoot, resolvedArgs } = resolveWorkspaceArgs(workspaceRoot, toolName, args);
|
|
979
|
+
let result;
|
|
818
980
|
switch (toolName) {
|
|
819
981
|
case 'read_file':
|
|
820
|
-
|
|
982
|
+
result = await readFile(effectiveRoot, resolvedArgs.filePath, resolvedArgs.startLine, resolvedArgs.endLine, resolvedArgs.targetElements);
|
|
983
|
+
break;
|
|
821
984
|
case 'write_file':
|
|
822
|
-
|
|
985
|
+
result = await writeFile(effectiveRoot, resolvedArgs.filePath, resolvedArgs.content);
|
|
986
|
+
break;
|
|
823
987
|
case 'delete_file':
|
|
824
|
-
|
|
988
|
+
result = await deleteFile(effectiveRoot, resolvedArgs.filePath);
|
|
989
|
+
break;
|
|
825
990
|
case 'rename_file':
|
|
826
|
-
|
|
991
|
+
result = await renameFile(effectiveRoot, resolvedArgs.sourcePath, resolvedArgs.targetPath);
|
|
992
|
+
break;
|
|
827
993
|
case 'modify_file':
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
994
|
+
result = await modifyFile(effectiveRoot, resolvedArgs.filePath, resolvedArgs.edits);
|
|
995
|
+
break;
|
|
996
|
+
case 'list_directory': {
|
|
997
|
+
const dirPath = resolvedArgs.dirPath;
|
|
998
|
+
// Handle @all — list all registered workspace roots
|
|
999
|
+
if (dirPath === '@all') {
|
|
1000
|
+
const allRoots = workspaceRegistry.getAllRoots(workspaceRoot);
|
|
1001
|
+
const sections = [];
|
|
1002
|
+
for (const { alias, root } of allRoots) {
|
|
1003
|
+
const label = alias ? `@${alias}` : '(primary)';
|
|
1004
|
+
const subResult = await listDirectory(root, '.', 2);
|
|
1005
|
+
sections.push(`=== Workspace: ${label} [${root}] ===\n${subResult.output}`);
|
|
1006
|
+
}
|
|
1007
|
+
result = { output: sections.join('\n\n') };
|
|
1008
|
+
}
|
|
1009
|
+
else {
|
|
1010
|
+
result = await listDirectory(effectiveRoot, dirPath, resolvedArgs.maxDepth ?? 3);
|
|
1011
|
+
}
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
831
1014
|
case 'run_command':
|
|
832
|
-
|
|
1015
|
+
result = await runCommand(effectiveRoot, resolvedArgs.command, abortSignal);
|
|
1016
|
+
break;
|
|
833
1017
|
case 'run_debug_script':
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1018
|
+
result = await runDebugScript(effectiveRoot, resolvedArgs.language, resolvedArgs.code, abortSignal);
|
|
1019
|
+
break;
|
|
1020
|
+
case 'grep_search': {
|
|
1021
|
+
const wsParam = resolvedArgs.workspace;
|
|
1022
|
+
if (wsParam === 'all') {
|
|
1023
|
+
// Cross-workspace search across all registered workspaces
|
|
1024
|
+
result = await crossWorkspaceGrep(workspaceRoot, resolvedArgs.pattern, resolvedArgs.fileGlob, abortSignal);
|
|
1025
|
+
}
|
|
1026
|
+
else if (wsParam && workspaceRegistry.get(wsParam)) {
|
|
1027
|
+
// Search in a specific external workspace
|
|
1028
|
+
const ws = workspaceRegistry.get(wsParam);
|
|
1029
|
+
result = await grepSearch(ws.absolutePath, resolvedArgs.pattern, resolvedArgs.fileGlob, abortSignal);
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
result = await grepSearch(effectiveRoot, resolvedArgs.pattern, resolvedArgs.fileGlob, abortSignal);
|
|
1033
|
+
}
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
case 'semantic_search': {
|
|
1037
|
+
const query = args.query;
|
|
1038
|
+
const topK = args.topK || 5;
|
|
1039
|
+
let output = '';
|
|
1040
|
+
try {
|
|
1041
|
+
const { getEmbeddingIndex } = await import('./embeddingIndex.js');
|
|
1042
|
+
const index = getEmbeddingIndex();
|
|
1043
|
+
if (!index.isReady()) {
|
|
1044
|
+
const loaded = await index.load(workspaceRoot);
|
|
1045
|
+
if (!loaded) {
|
|
1046
|
+
await index.buildIndex(workspaceRoot);
|
|
1047
|
+
await index.save(workspaceRoot);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const results = await index.search(query, topK);
|
|
1051
|
+
if (results.length === 0) {
|
|
1052
|
+
output = 'No semantically similar code found. (Index might be empty or embedding failed)';
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
output = results
|
|
1056
|
+
.map((r) => `[Score: ${r.score.toFixed(3)}] ${r.filePath}:${r.startLine}-${r.endLine}\n${r.preview}`)
|
|
1057
|
+
.join('\n---\n');
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
catch (e) {
|
|
1061
|
+
output = `Semantic search failed: ${e.message}`;
|
|
1062
|
+
}
|
|
1063
|
+
result = { output };
|
|
1064
|
+
break;
|
|
1065
|
+
}
|
|
837
1066
|
case 'find_dependencies':
|
|
838
|
-
|
|
1067
|
+
result = await traceDependencies(effectiveRoot, resolvedArgs.filePath, resolvedArgs.direction, resolvedArgs.maxDepth);
|
|
1068
|
+
break;
|
|
839
1069
|
case 'find_recent_changes':
|
|
840
|
-
|
|
1070
|
+
result = await findRecentChanges(effectiveRoot, resolvedArgs.dirPath, resolvedArgs.minutes, resolvedArgs.maxDepth);
|
|
1071
|
+
break;
|
|
841
1072
|
default:
|
|
842
|
-
|
|
1073
|
+
result = { output: '', error: `Unknown tool: "${toolName}"` };
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
if (result.error) {
|
|
1077
|
+
const collector = getMetricCollector();
|
|
1078
|
+
if (collector)
|
|
1079
|
+
collector.recordToolFailure(toolName);
|
|
843
1080
|
}
|
|
1081
|
+
return result;
|
|
844
1082
|
}
|
package/dist/services/agent.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* 5. **Self-Correction Pipeline**: Compares modified files against TS/Linter diagnostics,
|
|
16
16
|
* providing feedback on syntax or semantic failures back to the AI for self-healing.
|
|
17
17
|
*/
|
|
18
|
+
import { AsyncInputHandler } from './agent/inputHandler.js';
|
|
18
19
|
export { AsyncInputHandler } from './agent/inputHandler.js';
|
|
19
20
|
/**
|
|
20
21
|
* Starts and orchestrates the primary interactive command-line interface (REPL) loop.
|
|
@@ -48,3 +49,10 @@ export { AsyncInputHandler } from './agent/inputHandler.js';
|
|
|
48
49
|
* @param version - The Semantic Version string of the active tool distribution.
|
|
49
50
|
*/
|
|
50
51
|
export declare function startAgentLoop(workspaceRoot: string, version: string): Promise<void>;
|
|
52
|
+
export declare function executeSingleTurn(workspaceRoot: string, userInput: string, chat: any, inputHandler: AsyncInputHandler, chatSessionState: {
|
|
53
|
+
id: string;
|
|
54
|
+
title: string;
|
|
55
|
+
}, isPlanMode: boolean, cachedContextResult?: any): Promise<{
|
|
56
|
+
planModeReturn?: string;
|
|
57
|
+
contextResult?: any;
|
|
58
|
+
} | void>;
|