minovative-mind-cli 2.1.1 → 2.1.3
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 +4 -1
- package/dist/commands/chat.js +5 -2
- package/dist/services/agent/slashCommands.js +1 -14
- package/dist/services/agent-tools.d.ts +0 -2
- package/dist/services/agent-tools.js +141 -59
- package/dist/services/agent.js +3 -23
- package/dist/services/ai.js +0 -23
- package/dist/services/chatHistoryService.js +1 -1
- package/dist/services/contextAgent.js +0 -9
- package/dist/services/orchestration/investigationAgent.js +0 -30
- package/dist/services/orchestration/orchestrator.js +13 -6
- package/dist/services/orchestration/subAgent.d.ts +2 -1
- package/dist/services/orchestration/subAgent.js +6 -1
- package/dist/services/proxyClient.d.ts +0 -25
- package/dist/services/proxyClient.js +0 -60
- package/dist/utils/config.d.ts +0 -1
- package/dist/utils/config.js +0 -1
- package/dist/utils/projectStorage.js +1 -2
- package/dist/utils/systemPrompts.d.ts +1 -1
- package/dist/utils/systemPrompts.js +0 -1
- package/oclif.manifest.json +2 -2
- package/package.json +1 -1
- package/dist/services/embeddingIndex.d.ts +0 -82
- package/dist/services/embeddingIndex.js +0 -613
package/README.md
CHANGED
|
@@ -15,6 +15,9 @@ the build/performance metrics are green.
|
|
|
15
15
|
[](https://oclif.io)
|
|
16
16
|
[](https://npmjs.org/package/minovative-mind-cli)
|
|
17
17
|
[](https://npmjs.org/package/minovative-mind-cli)
|
|
18
|
+
[](https://nodejs.org)
|
|
19
|
+
[](https://www.typescriptlang.org/)
|
|
20
|
+
[](https://github.com/quarantiine/minovative-mind-cli/blob/main/LICENSE.md)
|
|
18
21
|
|
|
19
22
|
- Official Website: [Main Website](https://www.minovativemind.dev/)
|
|
20
23
|
- Latest Updates: [Updates](https://www.minovativemind.dev/updates)
|
|
@@ -121,7 +124,7 @@ Hot-swap during a session using `/models`:
|
|
|
121
124
|
|
|
122
125
|
Minovative Mind CLI doesn't restrict you to a single repository. You can link multiple external workspaces to your current session and the AI will seamlessly operate across all of them simultaneously.
|
|
123
126
|
|
|
124
|
-
By prefixing file paths with `@alias/` (e.g. `@backend/src/api.ts` and `@frontend/src/App.tsx`), the Context Agent
|
|
127
|
+
By prefixing file paths with `@alias/` (e.g. `@backend/src/api.ts` and `@frontend/src/App.tsx`), the Context Agent and Thread Agents can investigate, refactor, and coordinate changes across your entire tech stack in a single prompt. The terminal highlights cross-workspace actions with a blue `[alias]` visual tag. Use the "Edit Workspace" menu option to configure your linked projects.
|
|
125
128
|
|
|
126
129
|
---
|
|
127
130
|
|
package/dist/commands/chat.js
CHANGED
|
@@ -29,7 +29,6 @@ Inside the chat session, you can use the following commands in the slash menu:
|
|
|
29
29
|
/debug - Debug tests or command execution in a sandbox loop
|
|
30
30
|
/auto-approve - Toggle automatic approval of tool/command runs
|
|
31
31
|
/sub-agents - Toggle the MMAAK Engine for parallel investigation and execution
|
|
32
|
-
/semantic-search - Toggle local vector index capabilities
|
|
33
32
|
/workspaces - Manage external workspaces for cross-project development
|
|
34
33
|
/stats - View current session statistics and configuration
|
|
35
34
|
/commit - Commit current workspace changes to Git
|
|
@@ -56,9 +55,13 @@ Chat Controls:
|
|
|
56
55
|
console.clear();
|
|
57
56
|
// Setup update notifier
|
|
58
57
|
const pkg = JSON.parse(await fs.promises.readFile(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
59
|
-
updateNotifier({ pkg })
|
|
58
|
+
const notifier = updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 }); // Check every hour in background
|
|
59
|
+
notifier.notify();
|
|
60
60
|
printLogo();
|
|
61
61
|
p.intro(`${brandBg(' Minovative Mind CLI ')} ${pc.dim('v' + this.config.version)}`);
|
|
62
|
+
if (notifier.update && notifier.update.latest !== this.config.version) {
|
|
63
|
+
p.log.warn(`Update available! ${pc.red(this.config.version)} → ${pc.green(notifier.update.latest)}\nRun ${pc.cyan(`npm i -g ${pkg.name}`)} to update.`);
|
|
64
|
+
}
|
|
62
65
|
// Check authentication
|
|
63
66
|
let idToken = await getAuthorizedIdToken();
|
|
64
67
|
if (!idToken) {
|
|
@@ -10,7 +10,7 @@ import { changeLogger } from '../changeLogger.js';
|
|
|
10
10
|
import { chatHistoryService } from '../chatHistoryService.js';
|
|
11
11
|
import { printLogo, brandBg, brandFg } from '../../utils/logo.js';
|
|
12
12
|
import { readPaste } from '../../utils/paste.js';
|
|
13
|
-
import { setApprovalMode, getApprovalMode, isSubAgentsEnabled, setSubAgentsEnabled,
|
|
13
|
+
import { setApprovalMode, getApprovalMode, isSubAgentsEnabled, setSubAgentsEnabled, } from '../agent-tools.js';
|
|
14
14
|
import { ProxyChatSession, setGlobalActiveModel, getGlobalActiveModel, getGlobalLatestUsageMetadata } from '../ai.js';
|
|
15
15
|
const execAsync = promisify(exec);
|
|
16
16
|
/**
|
|
@@ -128,17 +128,6 @@ export async function handleSlashCommand(command, context) {
|
|
|
128
128
|
}
|
|
129
129
|
return { shouldContinue: true };
|
|
130
130
|
}
|
|
131
|
-
if (lowerCommand === '/semantic-search') {
|
|
132
|
-
if (isSemanticSearchEnabled()) {
|
|
133
|
-
setSemanticSearchEnabled(false);
|
|
134
|
-
p.log.success('Semantic search disabled. The vector indexing layer is now turned off.');
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
setSemanticSearchEnabled(true);
|
|
138
|
-
p.log.success('Semantic search enabled. The AST-aware vector index is active.');
|
|
139
|
-
}
|
|
140
|
-
return { shouldContinue: true };
|
|
141
|
-
}
|
|
142
131
|
if (lowerCommand === '/stats') {
|
|
143
132
|
const latestUsage = getGlobalLatestUsageMetadata();
|
|
144
133
|
const currentModel = chat.getModel();
|
|
@@ -146,14 +135,12 @@ export async function handleSlashCommand(command, context) {
|
|
|
146
135
|
const displayModel = globalModel === 'auto' ? `Auto (Last turn: ${currentModel})` : currentModel;
|
|
147
136
|
const autoApprove = getApprovalMode() === 'skip-all' ? 'Enabled' : 'Disabled';
|
|
148
137
|
const subAgents = isSubAgentsEnabled() ? 'Enabled' : 'Disabled';
|
|
149
|
-
const semanticSearch = isSemanticSearchEnabled() ? 'Enabled' : 'Disabled';
|
|
150
138
|
const planMode = context.isPlanMode ? 'Enabled' : 'Disabled';
|
|
151
139
|
p.log.step(pc.magenta('📊 Session Statistics & Status'));
|
|
152
140
|
console.log(pc.dim('----------------------------------------'));
|
|
153
141
|
console.log(`${pc.bold('AI Model:')} ${pc.cyan(displayModel)}`);
|
|
154
142
|
console.log(`${pc.bold('Auto-Approve:')} ${autoApprove === 'Enabled' ? pc.green(autoApprove) : pc.yellow(autoApprove)}`);
|
|
155
143
|
console.log(`${pc.bold('Sub-Agents:')} ${subAgents === 'Enabled' ? pc.green(subAgents) : pc.yellow(subAgents)}`);
|
|
156
|
-
console.log(`${pc.bold('Semantic Search:')} ${semanticSearch === 'Enabled' ? pc.green(semanticSearch) : pc.yellow(semanticSearch)}`);
|
|
157
144
|
console.log(`${pc.bold('Plan Mode:')} ${planMode === 'Enabled' ? pc.green(planMode) : pc.yellow(planMode)}`);
|
|
158
145
|
const debugMode = isDebugOn() ? 'Enabled' : 'Disabled';
|
|
159
146
|
console.log(`${pc.bold('Debug Log:')} ${debugMode === 'Enabled' ? pc.green(debugMode) : pc.yellow(debugMode)}`);
|
|
@@ -7,8 +7,6 @@ export interface ToolResult {
|
|
|
7
7
|
data: string;
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
-
export declare function isSemanticSearchEnabled(): boolean;
|
|
11
|
-
export declare function setSemanticSearchEnabled(val: boolean): void;
|
|
12
10
|
export declare function getToolDeclarations(): FunctionDeclaration[];
|
|
13
11
|
/**
|
|
14
12
|
* FunctionDeclaration-compatible schema objects that describe
|
|
@@ -17,18 +17,8 @@ import { extractSymbols } from '../utils/symbolExtractor.js';
|
|
|
17
17
|
import { getMetricCollector } from './metrics.js';
|
|
18
18
|
const execAsync = promisify(exec);
|
|
19
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
20
|
export function getToolDeclarations() {
|
|
28
|
-
|
|
29
|
-
return toolDeclarations;
|
|
30
|
-
}
|
|
31
|
-
return toolDeclarations.filter((t) => t.name !== 'semantic_search');
|
|
21
|
+
return toolDeclarations;
|
|
32
22
|
}
|
|
33
23
|
/**
|
|
34
24
|
* FunctionDeclaration-compatible schema objects that describe
|
|
@@ -259,24 +249,6 @@ export const toolDeclarations = [
|
|
|
259
249
|
required: ['language', 'code'],
|
|
260
250
|
},
|
|
261
251
|
},
|
|
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
|
-
},
|
|
280
252
|
];
|
|
281
253
|
let currentApprovalMode = 'ask';
|
|
282
254
|
export function getApprovalMode() {
|
|
@@ -361,7 +333,147 @@ export async function readFile(workspaceRoot, filePath, startLine, endLine, targ
|
|
|
361
333
|
inlineData: { mimeType: 'application/pdf', data: base64Data },
|
|
362
334
|
};
|
|
363
335
|
}
|
|
336
|
+
// Prevent reading massive lockfiles
|
|
337
|
+
const isLockfile = ['package-lock.json', 'yarn.lock', 'poetry.lock', 'pnpm-lock.yaml'].some(file => filePath.toLowerCase().endsWith(file));
|
|
338
|
+
if (isLockfile) {
|
|
339
|
+
return {
|
|
340
|
+
output: '',
|
|
341
|
+
error: `Do not read lockfiles directly as they waste thousands of tokens. Please read the 'package.json', 'pyproject.toml', or equivalent source file to see dependencies.`,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
// Handle SQLite databases natively by dumping the schema
|
|
345
|
+
if (filePath.toLowerCase().endsWith('.sqlite') || filePath.toLowerCase().endsWith('.db')) {
|
|
346
|
+
try {
|
|
347
|
+
const { stdout } = await execAsync(`sqlite3 "${absPath}" ".schema"`);
|
|
348
|
+
return {
|
|
349
|
+
output: `## Database Schema for ${path.basename(absPath)}\n\`\`\`sql\n${stdout}\n\`\`\``,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
catch (e) {
|
|
353
|
+
return {
|
|
354
|
+
output: '',
|
|
355
|
+
error: `Could not parse SQLite DB natively. Ensure 'sqlite3' CLI is installed. Error: ${e instanceof Error ? e.message : String(e)}`,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}
|
|
364
359
|
let content = await fs.readFile(absPath, 'utf-8');
|
|
360
|
+
// Handle Jupyter Notebooks natively
|
|
361
|
+
if (filePath.toLowerCase().endsWith('.ipynb')) {
|
|
362
|
+
try {
|
|
363
|
+
const notebook = JSON.parse(content);
|
|
364
|
+
if (notebook && Array.isArray(notebook.cells)) {
|
|
365
|
+
const cleanCells = [];
|
|
366
|
+
for (const cell of notebook.cells) {
|
|
367
|
+
const sourceArray = Array.isArray(cell.source) ? cell.source : [cell.source || ''];
|
|
368
|
+
const source = sourceArray.join('');
|
|
369
|
+
if (cell.cell_type === 'markdown') {
|
|
370
|
+
cleanCells.push(source);
|
|
371
|
+
}
|
|
372
|
+
else if (cell.cell_type === 'code') {
|
|
373
|
+
let cellStr = '```python\n' + source + '\n```';
|
|
374
|
+
if (cell.outputs && Array.isArray(cell.outputs)) {
|
|
375
|
+
const textOutputs = [];
|
|
376
|
+
for (const out of cell.outputs) {
|
|
377
|
+
if (out.output_type === 'stream' && out.text) {
|
|
378
|
+
const text = Array.isArray(out.text) ? out.text.join('') : out.text;
|
|
379
|
+
textOutputs.push(text);
|
|
380
|
+
}
|
|
381
|
+
else if (out.data) {
|
|
382
|
+
if (out.data['text/plain']) {
|
|
383
|
+
const text = Array.isArray(out.data['text/plain']) ? out.data['text/plain'].join('') : out.data['text/plain'];
|
|
384
|
+
textOutputs.push(text);
|
|
385
|
+
}
|
|
386
|
+
else if (out.data['image/png'] || out.data['image/jpeg'] || out.data['image/svg+xml']) {
|
|
387
|
+
textOutputs.push('[Image Output]');
|
|
388
|
+
}
|
|
389
|
+
else if (out.data['text/html']) {
|
|
390
|
+
textOutputs.push('[HTML Output]');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (textOutputs.length > 0) {
|
|
395
|
+
let joinedOutput = textOutputs.join('\n').trim();
|
|
396
|
+
const outLines = joinedOutput.split('\n');
|
|
397
|
+
if (outLines.length > 50) {
|
|
398
|
+
joinedOutput = outLines.slice(0, 50).join('\n') + '\n... (Output truncated)';
|
|
399
|
+
}
|
|
400
|
+
cellStr += '\n\n**Output:**\n```text\n' + joinedOutput + '\n```';
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
cleanCells.push(cellStr);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
content = cleanCells.join('\n\n');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch (err) {
|
|
410
|
+
content = `<!-- Failed to parse .ipynb cleanly, showing raw JSON -->\n${content}`;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// Handle CSV natively by formatting as a Markdown table
|
|
414
|
+
if (filePath.toLowerCase().endsWith('.csv')) {
|
|
415
|
+
try {
|
|
416
|
+
const rows = [];
|
|
417
|
+
let currentRow = [];
|
|
418
|
+
let currentCell = '';
|
|
419
|
+
let inQuotes = false;
|
|
420
|
+
for (let i = 0; i < content.length; i++) {
|
|
421
|
+
const char = content[i];
|
|
422
|
+
if (char === '"') {
|
|
423
|
+
if (inQuotes && content[i + 1] === '"') {
|
|
424
|
+
currentCell += '"';
|
|
425
|
+
i++;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
inQuotes = !inQuotes;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else if (char === ',' && !inQuotes) {
|
|
432
|
+
currentRow.push(currentCell.trim().replace(/\n/g, ' ').replace(/\|/g, '\\|'));
|
|
433
|
+
currentCell = '';
|
|
434
|
+
}
|
|
435
|
+
else if ((char === '\n' || char === '\r') && !inQuotes) {
|
|
436
|
+
if (char === '\r' && content[i + 1] === '\n')
|
|
437
|
+
i++;
|
|
438
|
+
currentRow.push(currentCell.trim().replace(/\n/g, ' ').replace(/\|/g, '\\|'));
|
|
439
|
+
if (currentRow.some(cell => cell.length > 0)) {
|
|
440
|
+
rows.push(currentRow);
|
|
441
|
+
}
|
|
442
|
+
currentRow = [];
|
|
443
|
+
currentCell = '';
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
currentCell += char;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (currentCell !== '' || currentRow.length > 0) {
|
|
450
|
+
currentRow.push(currentCell.trim().replace(/\n/g, ' ').replace(/\|/g, '\\|'));
|
|
451
|
+
if (currentRow.some(cell => cell.length > 0))
|
|
452
|
+
rows.push(currentRow);
|
|
453
|
+
}
|
|
454
|
+
if (rows.length > 0) {
|
|
455
|
+
const header = rows[0];
|
|
456
|
+
const MAX_CSV_ROWS = 250; // Generous truncation limit
|
|
457
|
+
const isTruncated = rows.length > MAX_CSV_ROWS;
|
|
458
|
+
const displayRows = isTruncated ? rows.slice(0, MAX_CSV_ROWS) : rows;
|
|
459
|
+
let mdTable = `| ${header.join(' | ')} |\n`;
|
|
460
|
+
mdTable += `| ${header.map(() => '---').join(' | ')} |\n`;
|
|
461
|
+
for (let i = 1; i < displayRows.length; i++) {
|
|
462
|
+
const row = displayRows[i];
|
|
463
|
+
while (row.length < header.length)
|
|
464
|
+
row.push('');
|
|
465
|
+
mdTable += `| ${row.slice(0, header.length).join(' | ')} |\n`;
|
|
466
|
+
}
|
|
467
|
+
if (isTruncated) {
|
|
468
|
+
mdTable += `\n*(Note: CSV truncated from ${rows.length} rows to ${MAX_CSV_ROWS} rows for safe viewing)*\n`;
|
|
469
|
+
}
|
|
470
|
+
content = mdTable;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
catch (err) {
|
|
474
|
+
content = `<!-- Failed to parse .csv as table, showing raw text -->\n${content}`;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
365
477
|
if (targetElements && targetElements.length > 0) {
|
|
366
478
|
content = extractSymbols(content, filePath, targetElements);
|
|
367
479
|
}
|
|
@@ -1033,36 +1145,6 @@ export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
|
|
|
1033
1145
|
}
|
|
1034
1146
|
break;
|
|
1035
1147
|
}
|
|
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
|
-
}
|
|
1066
1148
|
case 'find_dependencies':
|
|
1067
1149
|
result = await traceDependencies(effectiveRoot, resolvedArgs.filePath, resolvedArgs.direction, resolvedArgs.maxDepth);
|
|
1068
1150
|
break;
|
package/dist/services/agent.js
CHANGED
|
@@ -135,11 +135,6 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
135
135
|
{ value: '/debug', label: '/debug', hint: 'Toggle internal debug logs' },
|
|
136
136
|
{ value: '/auto-approve', label: '/auto-approve', hint: 'Approve all future terminal commands' },
|
|
137
137
|
{ value: '/sub-agents', label: '/sub-agents', hint: 'Toggle the MMAAK Engine for parallel investigation and execution' },
|
|
138
|
-
{
|
|
139
|
-
value: '/semantic-search',
|
|
140
|
-
label: '/semantic-search',
|
|
141
|
-
hint: 'Toggle local vector index capabilities for better search',
|
|
142
|
-
},
|
|
143
138
|
{ value: '/workspaces', label: '/workspaces', hint: 'Manage external workspaces for cross-project development' },
|
|
144
139
|
{ value: '/stats', label: '/stats', hint: 'View current session statistics and configuration' },
|
|
145
140
|
{ value: '/commit', label: '/commit', hint: 'Auto-commit changes with AI message' },
|
|
@@ -548,21 +543,6 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
548
543
|
// the user can still use /revert to undo the partial file mutations.
|
|
549
544
|
const changedFiles = changeLogger.getChangedFiles();
|
|
550
545
|
changeLogger.commitChangeSet();
|
|
551
|
-
// Delta-update the embedding index if there were changes
|
|
552
|
-
if (changedFiles.length > 0) {
|
|
553
|
-
try {
|
|
554
|
-
const { getEmbeddingIndex } = await import('./embeddingIndex.js');
|
|
555
|
-
const index = getEmbeddingIndex();
|
|
556
|
-
// Only update if it's already loaded in memory
|
|
557
|
-
if (index.isReady()) {
|
|
558
|
-
await index.updateIndex(workspaceRoot, changedFiles);
|
|
559
|
-
await index.save(workspaceRoot);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
catch (e) {
|
|
563
|
-
debugLog(`Failed to delta-update embedding index: ${e.message}`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
546
|
}
|
|
567
547
|
});
|
|
568
548
|
}
|
|
@@ -635,10 +615,10 @@ async function compressContextFiles(workspaceRoot, contextResult) {
|
|
|
635
615
|
}
|
|
636
616
|
}
|
|
637
617
|
if (cacheUpdated) {
|
|
638
|
-
// Keep cache size manageable by restricting to recent
|
|
618
|
+
// Keep cache size manageable by restricting to recent 500 file entries
|
|
639
619
|
const keys = Object.keys(cachedContext);
|
|
640
|
-
if (keys.length >
|
|
641
|
-
const toDelete = keys.length -
|
|
620
|
+
if (keys.length > 500) {
|
|
621
|
+
const toDelete = keys.length - 500;
|
|
642
622
|
for (let i = 0; i < toDelete; i++) {
|
|
643
623
|
delete cachedContext[keys[i]];
|
|
644
624
|
}
|
package/dist/services/ai.js
CHANGED
|
@@ -487,29 +487,6 @@ export function getContextToolDeclarations() {
|
|
|
487
487
|
required: ['query'],
|
|
488
488
|
},
|
|
489
489
|
},
|
|
490
|
-
{
|
|
491
|
-
name: 'semantic_search',
|
|
492
|
-
description: 'Search the codebase by meaning and concept rather than exact text match. ' +
|
|
493
|
-
'Use this when you need to find code related to a concept, pattern, or behavior ' +
|
|
494
|
-
"but don't know the exact variable or function names to grep for. " +
|
|
495
|
-
'Examples: "error handling for API requests", "user authentication flow", ' +
|
|
496
|
-
'"database connection pooling logic". Returns ranked results with file paths, ' +
|
|
497
|
-
'line ranges, and similarity scores.',
|
|
498
|
-
parameters: {
|
|
499
|
-
type: 'OBJECT',
|
|
500
|
-
properties: {
|
|
501
|
-
query: {
|
|
502
|
-
type: 'STRING',
|
|
503
|
-
description: "Natural language description of what you're looking for in the codebase.",
|
|
504
|
-
},
|
|
505
|
-
topK: {
|
|
506
|
-
type: 'NUMBER',
|
|
507
|
-
description: 'Number of results to return. Defaults to 5, maximum 15.',
|
|
508
|
-
},
|
|
509
|
-
},
|
|
510
|
-
required: ['query'],
|
|
511
|
-
},
|
|
512
|
-
},
|
|
513
490
|
],
|
|
514
491
|
},
|
|
515
492
|
];
|
|
@@ -434,15 +434,6 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
434
434
|
},
|
|
435
435
|
});
|
|
436
436
|
}
|
|
437
|
-
else if (call.name === 'semantic_search') {
|
|
438
|
-
const semRes = await executeTool(workspaceRoot, 'semantic_search', args);
|
|
439
|
-
functionResponses.push({
|
|
440
|
-
functionResponse: {
|
|
441
|
-
name: call.name,
|
|
442
|
-
response: { output: semRes.output },
|
|
443
|
-
},
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
437
|
else if (call.name === 'read_file') {
|
|
447
438
|
const readRes = await executeTool(workspaceRoot, 'read_file', args);
|
|
448
439
|
if (!readRes.error) {
|
|
@@ -240,36 +240,6 @@ export class InvestigationAgentRunner {
|
|
|
240
240
|
});
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
else if (call.name === 'semantic_search') {
|
|
244
|
-
const query = args.query;
|
|
245
|
-
const topK = args.topK || 5;
|
|
246
|
-
if (onProgress)
|
|
247
|
-
onProgress(`${logPrefix} Semantic search: "${query}"`);
|
|
248
|
-
let output = '';
|
|
249
|
-
try {
|
|
250
|
-
const { getEmbeddingIndex } = await import('../embeddingIndex.js');
|
|
251
|
-
const index = getEmbeddingIndex();
|
|
252
|
-
if (!index.isReady()) {
|
|
253
|
-
const loaded = await index.load(this.workspaceRoot);
|
|
254
|
-
if (!loaded) {
|
|
255
|
-
output = 'Semantic search index not available. Use search_codebase instead.';
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (index.isReady()) {
|
|
259
|
-
const results = await index.search(query, topK);
|
|
260
|
-
output =
|
|
261
|
-
results.length === 0
|
|
262
|
-
? 'No semantically similar code found.'
|
|
263
|
-
: results
|
|
264
|
-
.map((r) => `[Score: ${r.score.toFixed(3)}] ${r.filePath}:${r.startLine}-${r.endLine}\n${r.preview}`)
|
|
265
|
-
.join('\n---\n');
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
catch (e) {
|
|
269
|
-
output = `Semantic search failed: ${e.message}`;
|
|
270
|
-
}
|
|
271
|
-
functionResponses.push({ functionResponse: { name: call.name, response: { output } } });
|
|
272
|
-
}
|
|
273
243
|
else if (call.name === 'perform_web_search') {
|
|
274
244
|
if (onProgress)
|
|
275
245
|
onProgress(`${logPrefix} Web search: "${args.query}"`);
|
|
@@ -96,14 +96,21 @@ export class Orchestrator {
|
|
|
96
96
|
for (const wave of waves) {
|
|
97
97
|
if (signal.aborted)
|
|
98
98
|
break;
|
|
99
|
-
|
|
99
|
+
const taskDescriptions = wave.taskIds.map(taskId => {
|
|
100
|
+
const taskDef = graph.tasks.find(t => t.id === taskId);
|
|
101
|
+
return ` - ${pc.cyan(taskDef.id)}: ${pc.dim(taskDef.intent)}`;
|
|
102
|
+
}).join('\n');
|
|
103
|
+
p.log.step(pc.magenta(`Starting Wave ${wave.depth + 1} (${wave.taskIds.length} tasks):\n${taskDescriptions}`));
|
|
104
|
+
const s = p.spinner();
|
|
105
|
+
s.start(`Executing Wave ${wave.depth + 1}...`);
|
|
100
106
|
const wavePromises = wave.taskIds.map(taskId => {
|
|
101
107
|
const taskDef = graph.tasks.find(t => t.id === taskId);
|
|
102
108
|
const globalContext = `Objective:\n${objective}\n\nContext:\n${contextInjection}`;
|
|
103
|
-
return this.dispatchAgent(taskDef, globalContext, signal);
|
|
109
|
+
return this.dispatchAgent(taskDef, globalContext, signal, (msg) => s.message(msg));
|
|
104
110
|
});
|
|
105
111
|
// Run all agents in this wave concurrently
|
|
106
112
|
const results = await Promise.all(wavePromises);
|
|
113
|
+
s.stop(`Wave ${wave.depth + 1} execution finished.`);
|
|
107
114
|
// Post-wave evaluation
|
|
108
115
|
const failedCount = results.filter(r => !r.success).length;
|
|
109
116
|
if (failedCount > 0) {
|
|
@@ -179,8 +186,8 @@ export class Orchestrator {
|
|
|
179
186
|
/**
|
|
180
187
|
* Dispatches a single sub-agent and records its result.
|
|
181
188
|
*/
|
|
182
|
-
async dispatchAgent(taskDef, globalContext, signal) {
|
|
183
|
-
const runner = new SubAgentRunner(taskDef.id, taskDef.intent, this.workspaceRoot, this.bus, this.locks, globalContext);
|
|
189
|
+
async dispatchAgent(taskDef, globalContext, signal, onProgress) {
|
|
190
|
+
const runner = new SubAgentRunner(taskDef.id, taskDef.intent, this.workspaceRoot, this.bus, this.locks, globalContext, onProgress);
|
|
184
191
|
const result = await runner.execute(signal);
|
|
185
192
|
// Clean up any stray locks if the agent crashed or stalled
|
|
186
193
|
if (result.crashed) {
|
|
@@ -197,13 +204,13 @@ export class Orchestrator {
|
|
|
197
204
|
const stats = this.bus.getStats();
|
|
198
205
|
let totalTokens = 0;
|
|
199
206
|
let failedTasks = 0;
|
|
200
|
-
let finalSummary = '[Sub-Agent Orchestration Completed]
|
|
207
|
+
let finalSummary = '[Sub-Agent Orchestration Completed]\n\n';
|
|
201
208
|
for (const [taskId, res] of this.agentResults.entries()) {
|
|
202
209
|
totalTokens += res.creditsUsed;
|
|
203
210
|
if (!res.success)
|
|
204
211
|
failedTasks++;
|
|
205
212
|
debugLog(`Task ${taskId} summary: ${res.summary.substring(0, 100)}...`);
|
|
206
|
-
finalSummary += `**Task: ${taskId}
|
|
213
|
+
finalSummary += `**Task: ${taskId}**\nStatus: ${res.success ? 'Success' : 'Failed'}\n${res.summary}\n\n`;
|
|
207
214
|
}
|
|
208
215
|
p.log.info(`${pc.green('✓')} Sub-agent execution complete.\n` +
|
|
209
216
|
` Total tasks: ${graph.tasks.length} (${failedTasks} failed)\n` +
|
|
@@ -30,12 +30,13 @@ export declare class SubAgentRunner {
|
|
|
30
30
|
private readonly bus;
|
|
31
31
|
private readonly locks;
|
|
32
32
|
private readonly globalContext;
|
|
33
|
+
private readonly onProgress?;
|
|
33
34
|
private chat;
|
|
34
35
|
private lastHeartbeat;
|
|
35
36
|
private creditsUsed;
|
|
36
37
|
/** Max time without a tool call or response before the agent is considered stalled */
|
|
37
38
|
static readonly STALL_TIMEOUT_MS = 60000;
|
|
38
|
-
constructor(taskId: string, intent: string, workspaceRoot: string, bus: MessageBus, locks: FileLockRegistry, globalContext: string);
|
|
39
|
+
constructor(taskId: string, intent: string, workspaceRoot: string, bus: MessageBus, locks: FileLockRegistry, globalContext: string, onProgress?: ((msg: string) => void) | undefined);
|
|
39
40
|
/**
|
|
40
41
|
* Constructs the base system instruction for this specific agent.
|
|
41
42
|
*/
|
|
@@ -20,18 +20,20 @@ export class SubAgentRunner {
|
|
|
20
20
|
bus;
|
|
21
21
|
locks;
|
|
22
22
|
globalContext;
|
|
23
|
+
onProgress;
|
|
23
24
|
chat;
|
|
24
25
|
lastHeartbeat = Date.now();
|
|
25
26
|
creditsUsed = 0;
|
|
26
27
|
/** Max time without a tool call or response before the agent is considered stalled */
|
|
27
28
|
static STALL_TIMEOUT_MS = 60_000;
|
|
28
|
-
constructor(taskId, intent, workspaceRoot, bus, locks, globalContext) {
|
|
29
|
+
constructor(taskId, intent, workspaceRoot, bus, locks, globalContext, onProgress) {
|
|
29
30
|
this.taskId = taskId;
|
|
30
31
|
this.intent = intent;
|
|
31
32
|
this.workspaceRoot = workspaceRoot;
|
|
32
33
|
this.bus = bus;
|
|
33
34
|
this.locks = locks;
|
|
34
35
|
this.globalContext = globalContext;
|
|
36
|
+
this.onProgress = onProgress;
|
|
35
37
|
let model = getGlobalActiveModel();
|
|
36
38
|
if (model === GEMINI_MODELS.AUTO)
|
|
37
39
|
model = GEMINI_MODELS.FLASH_3_5;
|
|
@@ -123,6 +125,9 @@ export class SubAgentRunner {
|
|
|
123
125
|
break;
|
|
124
126
|
}
|
|
125
127
|
this.pingHeartbeat();
|
|
128
|
+
if (this.onProgress) {
|
|
129
|
+
this.onProgress(`[${this.taskId}] executing ${call.name}...`);
|
|
130
|
+
}
|
|
126
131
|
debugLog(`SubAgent [${this.taskId}]: Executing tool ${call.name}`);
|
|
127
132
|
let responseData;
|
|
128
133
|
try {
|
|
@@ -34,7 +34,6 @@ export interface ProxyUsageMetadata {
|
|
|
34
34
|
*/
|
|
35
35
|
export declare class ProxyClient {
|
|
36
36
|
private readonly PROXY_URL;
|
|
37
|
-
private readonly EMBED_URL;
|
|
38
37
|
/**
|
|
39
38
|
* Generates text, thoughts, or function calls via the secure Gemini proxy URL.
|
|
40
39
|
* Utilizes Server-Sent Events (SSE) to stream partial token responses back to the client.
|
|
@@ -61,28 +60,4 @@ export declare class ProxyClient {
|
|
|
61
60
|
usageMetadata?: ProxyUsageMetadata;
|
|
62
61
|
groundingMetadata?: any;
|
|
63
62
|
}>;
|
|
64
|
-
/**
|
|
65
|
-
* Embeds one or more text chunks via the secure embedding proxy endpoint.
|
|
66
|
-
* Uses the same Firebase auth pattern as generateFunctionCallViaProxy, but
|
|
67
|
-
* targets a separate Cloud Function optimized for embedding generation.
|
|
68
|
-
*
|
|
69
|
-
* Unlike the generative endpoint, embedding responses are small and atomic,
|
|
70
|
-
* so no SSE streaming is required — a single JSON response is returned.
|
|
71
|
-
*
|
|
72
|
-
* @param idToken - The Firebase ID token for authorization.
|
|
73
|
-
* @param texts - Array of text strings to embed. Batched by caller (max ~25 per call).
|
|
74
|
-
* @param taskType - Embedding task type hint for optimal retrieval quality.
|
|
75
|
-
* - 'RETRIEVAL_DOCUMENT': Used when indexing source code chunks.
|
|
76
|
-
* - 'RETRIEVAL_QUERY': Used when embedding a user's semantic search query.
|
|
77
|
-
* @returns The embedding vectors and usage metadata from the proxy.
|
|
78
|
-
* @throws {Error} If authentication fails (401), credits are insufficient (402), or network errors occur.
|
|
79
|
-
*/
|
|
80
|
-
embedTextsViaProxy(idToken: string, texts: string[], taskType?: 'RETRIEVAL_DOCUMENT' | 'RETRIEVAL_QUERY'): Promise<{
|
|
81
|
-
embeddings: number[][];
|
|
82
|
-
usage?: {
|
|
83
|
-
promptTokens: number;
|
|
84
|
-
creditsUsed: number;
|
|
85
|
-
remainingBalance: number;
|
|
86
|
-
};
|
|
87
|
-
}>;
|
|
88
63
|
}
|