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 CHANGED
@@ -15,6 +15,9 @@ the build/performance metrics are green.
15
15
  [![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io)
16
16
  [![Version](https://img.shields.io/npm/v/minovative-mind-cli.svg)](https://npmjs.org/package/minovative-mind-cli)
17
17
  [![Downloads/week](https://img.shields.io/npm/dw/minovative-mind-cli.svg)](https://npmjs.org/package/minovative-mind-cli)
18
+ [![Node.js Version](https://img.shields.io/node/v/minovative-mind-cli.svg)](https://nodejs.org)
19
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
20
+ [![License](https://img.shields.io/npm/l/minovative-mind-cli.svg)](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, Thread Agents, and Semantic Search tools 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.
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
 
@@ -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 }).notify();
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, isSemanticSearchEnabled, setSemanticSearchEnabled, } from '../agent-tools.js';
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
- if (_semanticSearchEnabled) {
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;
@@ -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 100 file entries
618
+ // Keep cache size manageable by restricting to recent 500 file entries
639
619
  const keys = Object.keys(cachedContext);
640
- if (keys.length > 100) {
641
- const toDelete = keys.length - 100;
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
  }
@@ -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
  ];
@@ -1,7 +1,7 @@
1
1
  import { readCache, writeCache } from '../utils/projectStorage.js';
2
2
  class ChatHistoryService {
3
3
  workspaceRoot = '';
4
- MAX_SESSIONS = 50;
4
+ MAX_SESSIONS = 250;
5
5
  init(workspaceRoot) {
6
6
  this.workspaceRoot = workspaceRoot;
7
7
  }
@@ -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
- p.log.step(pc.magenta(`Starting Wave ${wave.depth + 1}...`));
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]\\n\\n';
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}**\\nStatus: ${res.success ? 'Success' : 'Failed'}\\n${res.summary}\\n\\n`;
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
  }