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.
Files changed (79) hide show
  1. package/README.md +59 -45
  2. package/dist/commands/chat.js +10 -2
  3. package/dist/services/agent/slashCommands.js +369 -42
  4. package/dist/services/agent/toolLoop.d.ts +1 -1
  5. package/dist/services/agent/toolLoop.js +7 -2
  6. package/dist/services/agent/types.d.ts +2 -0
  7. package/dist/services/agent-tools.d.ts +9 -4
  8. package/dist/services/agent-tools.js +272 -34
  9. package/dist/services/agent.d.ts +8 -0
  10. package/dist/services/agent.js +288 -40
  11. package/dist/services/ai.d.ts +19 -5
  12. package/dist/services/ai.js +182 -36
  13. package/dist/services/changeLogger.d.ts +142 -0
  14. package/dist/services/changeLogger.js +132 -3
  15. package/dist/services/contextAgent.d.ts +6 -1
  16. package/dist/services/contextAgent.js +112 -19
  17. package/dist/services/embeddingIndex.d.ts +82 -0
  18. package/dist/services/embeddingIndex.js +613 -0
  19. package/dist/services/investigationComplexity.d.ts +45 -0
  20. package/dist/services/investigationComplexity.js +91 -0
  21. package/dist/services/metrics.d.ts +18 -0
  22. package/dist/services/metrics.js +7 -0
  23. package/dist/services/orchestration/fileLockRegistry.d.ts +125 -0
  24. package/dist/services/orchestration/fileLockRegistry.js +276 -0
  25. package/dist/services/orchestration/investigationAgent.d.ts +85 -0
  26. package/dist/services/orchestration/investigationAgent.js +362 -0
  27. package/dist/services/orchestration/investigationOrchestrator.d.ts +53 -0
  28. package/dist/services/orchestration/investigationOrchestrator.js +180 -0
  29. package/dist/services/orchestration/messageBus.d.ts +162 -0
  30. package/dist/services/orchestration/messageBus.js +225 -0
  31. package/dist/services/orchestration/orchestrator.d.ts +45 -0
  32. package/dist/services/orchestration/orchestrator.js +217 -0
  33. package/dist/services/orchestration/readCache.d.ts +79 -0
  34. package/dist/services/orchestration/readCache.js +108 -0
  35. package/dist/services/orchestration/scopedTools.d.ts +57 -0
  36. package/dist/services/orchestration/scopedTools.js +172 -0
  37. package/dist/services/orchestration/subAgent.d.ts +58 -0
  38. package/dist/services/orchestration/subAgent.js +190 -0
  39. package/dist/services/orchestration/taskGraph.d.ts +129 -0
  40. package/dist/services/orchestration/taskGraph.js +254 -0
  41. package/dist/services/proxyClient.d.ts +25 -0
  42. package/dist/services/proxyClient.js +60 -0
  43. package/dist/services/workspaceRegistry.d.ts +137 -0
  44. package/dist/services/workspaceRegistry.js +270 -0
  45. package/dist/utils/asyncContext.d.ts +16 -0
  46. package/dist/utils/asyncContext.js +25 -0
  47. package/dist/utils/config.d.ts +3 -1
  48. package/dist/utils/config.js +3 -1
  49. package/dist/utils/contextPrompts.js +10 -3
  50. package/dist/utils/dependencyTracer/modules/api.d.ts +9 -0
  51. package/dist/utils/dependencyTracer/modules/api.js +62 -0
  52. package/dist/utils/dependencyTracer/modules/graph.d.ts +9 -0
  53. package/dist/utils/dependencyTracer/modules/graph.js +23 -0
  54. package/dist/utils/dependencyTracer/modules/profiles.d.ts +7 -0
  55. package/dist/utils/dependencyTracer/modules/profiles.js +120 -0
  56. package/dist/utils/dependencyTracer/modules/resolver.d.ts +7 -0
  57. package/dist/utils/dependencyTracer/modules/resolver.js +51 -0
  58. package/dist/utils/dependencyTracer/modules/types.d.ts +4 -0
  59. package/dist/utils/dependencyTracer/modules/types.js +1 -0
  60. package/dist/utils/dependencyTracer/modules/walker.d.ts +1 -0
  61. package/dist/utils/dependencyTracer/modules/walker.js +48 -0
  62. package/dist/utils/dependencyTracer.js +31 -17
  63. package/dist/utils/excludedExtensions.js +0 -1
  64. package/dist/utils/historyPrompt.d.ts +9 -0
  65. package/dist/utils/historyPrompt.js +87 -0
  66. package/dist/utils/logo.js +7 -7
  67. package/dist/utils/paste.d.ts +21 -0
  68. package/dist/utils/paste.js +22 -1
  69. package/dist/utils/pathSecurity.d.ts +31 -0
  70. package/dist/utils/pathSecurity.js +48 -0
  71. package/dist/utils/profiles.d.ts +2 -0
  72. package/dist/utils/profiles.js +44 -0
  73. package/dist/utils/projectStorage.js +10 -7
  74. package/dist/utils/systemPrompts.d.ts +6 -3
  75. package/dist/utils/systemPrompts.js +111 -6
  76. package/dist/utils/types.d.ts +33 -0
  77. package/dist/utils/types.js +1 -0
  78. package/oclif.manifest.json +2 -2
  79. package/package.json +4 -3
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @fileoverview Task Graph for Sub-Agent Orchestration.
3
+ *
4
+ * Defines the data structures and algorithms for decomposing a user request into
5
+ * a directed acyclic graph (DAG) of parallelizable sub-tasks.
6
+ *
7
+ * Key capabilities:
8
+ * - **Kahn's Algorithm** for topological sort with cycle detection
9
+ * - **Execution Wave computation** for maximally parallel dispatch scheduling
10
+ * - **File conflict detection** for lock pre-allocation ordering
11
+ * - **Disconnected graph support** — floating tasks launch immediately in Wave 1
12
+ */
13
+ /**
14
+ * A structured plan produced by the PM Agent. Contains all the information
15
+ * needed to dispatch, schedule, and coordinate parallel sub-agents.
16
+ */
17
+ export interface TaskGraph {
18
+ /** High-level objective from the user */
19
+ objective: string;
20
+ /** Ordered list of sub-tasks. Each task can span multiple files. */
21
+ tasks: SubTask[];
22
+ /** Global constraints all sub-agents must respect */
23
+ constraints: string[];
24
+ }
25
+ /**
26
+ * A single unit of work assigned to one sub-agent.
27
+ * Defined by intent (what to do), scope (which files), and ordering (dependencies).
28
+ */
29
+ export interface SubTask {
30
+ /** Unique identifier for this task (e.g., 'task-1', 'setup-redis') */
31
+ id: string;
32
+ /** Human-readable description of what this sub-agent must accomplish */
33
+ intent: string;
34
+ /** All files this sub-agent will work on (can be many) */
35
+ targetFiles: string[];
36
+ /** Files this sub-agent may read but NOT modify */
37
+ readOnlyFiles: string[];
38
+ /** IDs of tasks that must complete before this one starts */
39
+ dependsOn: string[];
40
+ /** What this sub-agent should export for downstream tasks */
41
+ exports: string[];
42
+ }
43
+ /**
44
+ * An execution wave groups tasks at the same depth in the dependency graph.
45
+ * All tasks within a wave can run in parallel. Waves execute sequentially.
46
+ */
47
+ export interface ExecutionWave {
48
+ /** 0-indexed wave depth */
49
+ depth: number;
50
+ /** Task IDs that can run in parallel at this depth */
51
+ taskIds: string[];
52
+ }
53
+ /**
54
+ * Thrown when Kahn's algorithm detects a cyclic dependency in the task graph.
55
+ * Contains the specific nodes involved so the PM can self-correct.
56
+ */
57
+ export declare class CyclicDependencyError extends Error {
58
+ /** Human-readable description of the cycle nodes and their dependencies */
59
+ readonly cycleNodes: string[];
60
+ constructor(
61
+ /** Human-readable description of the cycle nodes and their dependencies */
62
+ cycleNodes: string[]);
63
+ }
64
+ /**
65
+ * Thrown when a task references a dependency that doesn't exist in the graph.
66
+ */
67
+ export declare class InvalidDependencyError extends Error {
68
+ readonly taskId: string;
69
+ readonly missingDependencyId: string;
70
+ constructor(taskId: string, missingDependencyId: string);
71
+ }
72
+ /**
73
+ * Validates a TaskGraph for cyclic dependencies using Kahn's Algorithm
74
+ * for topological sorting.
75
+ *
76
+ * - **Time complexity**: O(V + E) where V = tasks, E = dependency edges.
77
+ * - **Disconnected graph support**: Tasks with no dependencies AND no dependents
78
+ * naturally get in-degree 0 and appear immediately in the sorted output.
79
+ *
80
+ * @param graph - The task graph to validate.
81
+ * @returns The topological execution order (array of task IDs).
82
+ * @throws {CyclicDependencyError} If the graph contains a cycle.
83
+ * @throws {InvalidDependencyError} If a task references a non-existent dependency.
84
+ */
85
+ export declare function validateTaskGraph(graph: TaskGraph): string[];
86
+ /**
87
+ * Computes execution waves from a validated task graph. Each wave is a set of
88
+ * tasks that can run in parallel — all their dependencies are satisfied by
89
+ * tasks in earlier waves.
90
+ *
91
+ * **Disconnected tasks** (no deps, no one depends on them) land in Wave 0.
92
+ *
93
+ * @param graph - A validated (acyclic) task graph.
94
+ * @returns Array of execution waves ordered by depth.
95
+ */
96
+ export declare function computeExecutionWaves(graph: TaskGraph): ExecutionWave[];
97
+ /**
98
+ * Detects file-level conflicts between tasks in the same execution wave.
99
+ * If two tasks in the same wave share any `targetFiles`, they CANNOT run in
100
+ * parallel (circular wait deadlock risk). The orchestrator must serialize them.
101
+ *
102
+ * @param graph - A validated task graph.
103
+ * @param waves - The computed execution waves.
104
+ * @returns A map of wave depth → array of conflicting task ID pairs with their shared files.
105
+ */
106
+ export declare function detectFileConflicts(graph: TaskGraph, waves: ExecutionWave[]): Map<number, Array<{
107
+ taskA: string;
108
+ taskB: string;
109
+ sharedFiles: string[];
110
+ }>>;
111
+ /**
112
+ * Resolves file conflicts by splitting conflicted tasks out of their parallel
113
+ * wave and serializing them. Returns a new wave schedule where conflicting
114
+ * tasks are placed into sequential sub-waves.
115
+ *
116
+ * @param waves - The original execution waves.
117
+ * @param conflicts - The detected conflicts from `detectFileConflicts`.
118
+ * @returns A new array of execution waves with conflicts resolved.
119
+ */
120
+ export declare function resolveFileConflicts(waves: ExecutionWave[], conflicts: Map<number, Array<{
121
+ taskA: string;
122
+ taskB: string;
123
+ sharedFiles: string[];
124
+ }>>): ExecutionWave[];
125
+ /**
126
+ * Generates the system prompt sent to the PM Agent when Kahn's algorithm
127
+ * detects a cycle, requesting it to fix its own task graph.
128
+ */
129
+ export declare function buildCycleCorrectionPrompt(cycleNodes: string[]): string;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * @fileoverview Task Graph for Sub-Agent Orchestration.
3
+ *
4
+ * Defines the data structures and algorithms for decomposing a user request into
5
+ * a directed acyclic graph (DAG) of parallelizable sub-tasks.
6
+ *
7
+ * Key capabilities:
8
+ * - **Kahn's Algorithm** for topological sort with cycle detection
9
+ * - **Execution Wave computation** for maximally parallel dispatch scheduling
10
+ * - **File conflict detection** for lock pre-allocation ordering
11
+ * - **Disconnected graph support** — floating tasks launch immediately in Wave 1
12
+ */
13
+ import { debugLog } from '../../utils/logger.js';
14
+ // ─── Errors ──────────────────────────────────────────────────────────
15
+ /**
16
+ * Thrown when Kahn's algorithm detects a cyclic dependency in the task graph.
17
+ * Contains the specific nodes involved so the PM can self-correct.
18
+ */
19
+ export class CyclicDependencyError extends Error {
20
+ cycleNodes;
21
+ constructor(
22
+ /** Human-readable description of the cycle nodes and their dependencies */
23
+ cycleNodes) {
24
+ super(`Task graph contains a cyclic dependency:\n${cycleNodes.join('\n')}\n\n` +
25
+ `Tasks cannot depend on each other circularly.`);
26
+ this.cycleNodes = cycleNodes;
27
+ this.name = 'CyclicDependencyError';
28
+ }
29
+ }
30
+ /**
31
+ * Thrown when a task references a dependency that doesn't exist in the graph.
32
+ */
33
+ export class InvalidDependencyError extends Error {
34
+ taskId;
35
+ missingDependencyId;
36
+ constructor(taskId, missingDependencyId) {
37
+ super(`Task "${taskId}" depends on "${missingDependencyId}" which does not exist in the graph.`);
38
+ this.taskId = taskId;
39
+ this.missingDependencyId = missingDependencyId;
40
+ this.name = 'InvalidDependencyError';
41
+ }
42
+ }
43
+ // ─── Validation (Kahn's Algorithm) ───────────────────────────────────
44
+ /**
45
+ * Validates a TaskGraph for cyclic dependencies using Kahn's Algorithm
46
+ * for topological sorting.
47
+ *
48
+ * - **Time complexity**: O(V + E) where V = tasks, E = dependency edges.
49
+ * - **Disconnected graph support**: Tasks with no dependencies AND no dependents
50
+ * naturally get in-degree 0 and appear immediately in the sorted output.
51
+ *
52
+ * @param graph - The task graph to validate.
53
+ * @returns The topological execution order (array of task IDs).
54
+ * @throws {CyclicDependencyError} If the graph contains a cycle.
55
+ * @throws {InvalidDependencyError} If a task references a non-existent dependency.
56
+ */
57
+ export function validateTaskGraph(graph) {
58
+ const taskIds = new Set(graph.tasks.map(t => t.id));
59
+ const inDegree = new Map();
60
+ const adjacency = new Map();
61
+ // Initialize all nodes
62
+ for (const task of graph.tasks) {
63
+ inDegree.set(task.id, 0);
64
+ adjacency.set(task.id, []);
65
+ }
66
+ // Build adjacency list and compute in-degrees
67
+ for (const task of graph.tasks) {
68
+ for (const dep of task.dependsOn) {
69
+ // Validate that the dependency actually exists
70
+ if (!taskIds.has(dep)) {
71
+ throw new InvalidDependencyError(task.id, dep);
72
+ }
73
+ adjacency.get(dep).push(task.id);
74
+ inDegree.set(task.id, (inDegree.get(task.id) ?? 0) + 1);
75
+ }
76
+ }
77
+ // Seed the queue with all zero-dependency tasks (Wave 1 candidates)
78
+ const queue = [];
79
+ for (const [id, degree] of inDegree) {
80
+ if (degree === 0)
81
+ queue.push(id);
82
+ }
83
+ // Process the queue
84
+ const sorted = [];
85
+ while (queue.length > 0) {
86
+ const current = queue.shift();
87
+ sorted.push(current);
88
+ for (const neighbor of adjacency.get(current) ?? []) {
89
+ const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
90
+ inDegree.set(neighbor, newDegree);
91
+ if (newDegree === 0)
92
+ queue.push(neighbor);
93
+ }
94
+ }
95
+ // If not all nodes were processed, we have a cycle
96
+ if (sorted.length !== graph.tasks.length) {
97
+ const cycleNodes = graph.tasks
98
+ .filter(t => !sorted.includes(t.id))
99
+ .map(t => ` ${t.id} (depends on: ${t.dependsOn.join(', ')})`);
100
+ debugLog(`TaskGraph: Cycle detected in ${cycleNodes.length} nodes.`);
101
+ throw new CyclicDependencyError(cycleNodes);
102
+ }
103
+ debugLog(`TaskGraph: Valid topological order: [${sorted.join(' → ')}]`);
104
+ return sorted;
105
+ }
106
+ // ─── Execution Wave Computation ──────────────────────────────────────
107
+ /**
108
+ * Computes execution waves from a validated task graph. Each wave is a set of
109
+ * tasks that can run in parallel — all their dependencies are satisfied by
110
+ * tasks in earlier waves.
111
+ *
112
+ * **Disconnected tasks** (no deps, no one depends on them) land in Wave 0.
113
+ *
114
+ * @param graph - A validated (acyclic) task graph.
115
+ * @returns Array of execution waves ordered by depth.
116
+ */
117
+ export function computeExecutionWaves(graph) {
118
+ const taskMap = new Map(graph.tasks.map(t => [t.id, t]));
119
+ const depths = new Map();
120
+ // Compute the depth of each task (longest path from any root)
121
+ function computeDepth(taskId, visited = new Set()) {
122
+ if (depths.has(taskId))
123
+ return depths.get(taskId);
124
+ if (visited.has(taskId))
125
+ return 0; // Safety guard (should never hit post-validation)
126
+ visited.add(taskId);
127
+ const task = taskMap.get(taskId);
128
+ if (task.dependsOn.length === 0) {
129
+ depths.set(taskId, 0);
130
+ return 0;
131
+ }
132
+ const maxDepDep = Math.max(...task.dependsOn.map(dep => computeDepth(dep, visited)));
133
+ const depth = maxDepDep + 1;
134
+ depths.set(taskId, depth);
135
+ return depth;
136
+ }
137
+ for (const task of graph.tasks) {
138
+ computeDepth(task.id);
139
+ }
140
+ // Group tasks by depth into waves
141
+ const waveMap = new Map();
142
+ for (const [taskId, depth] of depths) {
143
+ if (!waveMap.has(depth))
144
+ waveMap.set(depth, []);
145
+ waveMap.get(depth).push(taskId);
146
+ }
147
+ // Sort waves by depth and return
148
+ const waves = Array.from(waveMap.entries())
149
+ .sort(([a], [b]) => a - b)
150
+ .map(([depth, taskIds]) => ({ depth, taskIds }));
151
+ debugLog(`TaskGraph: ${waves.length} execution wave(s): ` +
152
+ waves.map(w => `Wave ${w.depth} [${w.taskIds.join(', ')}]`).join(' → '));
153
+ return waves;
154
+ }
155
+ // ─── File Conflict Detection (Lock Pre-Allocation) ───────────────────
156
+ /**
157
+ * Detects file-level conflicts between tasks in the same execution wave.
158
+ * If two tasks in the same wave share any `targetFiles`, they CANNOT run in
159
+ * parallel (circular wait deadlock risk). The orchestrator must serialize them.
160
+ *
161
+ * @param graph - A validated task graph.
162
+ * @param waves - The computed execution waves.
163
+ * @returns A map of wave depth → array of conflicting task ID pairs with their shared files.
164
+ */
165
+ export function detectFileConflicts(graph, waves) {
166
+ const taskMap = new Map(graph.tasks.map(t => [t.id, t]));
167
+ const conflicts = new Map();
168
+ for (const wave of waves) {
169
+ if (wave.taskIds.length < 2)
170
+ continue; // Solo waves can't have conflicts
171
+ const waveConflicts = [];
172
+ // Check every pair of tasks in the wave for file intersection
173
+ for (let i = 0; i < wave.taskIds.length; i++) {
174
+ for (let j = i + 1; j < wave.taskIds.length; j++) {
175
+ const taskA = taskMap.get(wave.taskIds[i]);
176
+ const taskB = taskMap.get(wave.taskIds[j]);
177
+ const aTargets = new Set(taskA.targetFiles);
178
+ const sharedFiles = taskB.targetFiles.filter(f => aTargets.has(f));
179
+ if (sharedFiles.length > 0) {
180
+ waveConflicts.push({
181
+ taskA: taskA.id,
182
+ taskB: taskB.id,
183
+ sharedFiles,
184
+ });
185
+ }
186
+ }
187
+ }
188
+ if (waveConflicts.length > 0) {
189
+ conflicts.set(wave.depth, waveConflicts);
190
+ debugLog(`TaskGraph: Wave ${wave.depth} has ${waveConflicts.length} file conflict(s): ` +
191
+ waveConflicts
192
+ .map(c => `${c.taskA} ∩ ${c.taskB} on [${c.sharedFiles.join(', ')}]`)
193
+ .join('; '));
194
+ }
195
+ }
196
+ return conflicts;
197
+ }
198
+ /**
199
+ * Resolves file conflicts by splitting conflicted tasks out of their parallel
200
+ * wave and serializing them. Returns a new wave schedule where conflicting
201
+ * tasks are placed into sequential sub-waves.
202
+ *
203
+ * @param waves - The original execution waves.
204
+ * @param conflicts - The detected conflicts from `detectFileConflicts`.
205
+ * @returns A new array of execution waves with conflicts resolved.
206
+ */
207
+ export function resolveFileConflicts(waves, conflicts) {
208
+ if (conflicts.size === 0)
209
+ return waves;
210
+ const resolved = [];
211
+ let currentDepth = 0;
212
+ for (const wave of waves) {
213
+ const waveConflicts = conflicts.get(wave.depth);
214
+ if (!waveConflicts || waveConflicts.length === 0) {
215
+ // No conflicts in this wave — keep it as-is but reindex depth
216
+ resolved.push({ depth: currentDepth, taskIds: [...wave.taskIds] });
217
+ currentDepth++;
218
+ continue;
219
+ }
220
+ // Collect all conflicting task IDs
221
+ const conflictingIds = new Set();
222
+ for (const conflict of waveConflicts) {
223
+ conflictingIds.add(conflict.taskA);
224
+ conflictingIds.add(conflict.taskB);
225
+ }
226
+ // Non-conflicting tasks can still run in parallel
227
+ const parallelTasks = wave.taskIds.filter(id => !conflictingIds.has(id));
228
+ const serialTasks = wave.taskIds.filter(id => conflictingIds.has(id));
229
+ if (parallelTasks.length > 0) {
230
+ resolved.push({ depth: currentDepth, taskIds: parallelTasks });
231
+ currentDepth++;
232
+ }
233
+ // Serialize conflicting tasks into individual sub-waves
234
+ for (const taskId of serialTasks) {
235
+ resolved.push({ depth: currentDepth, taskIds: [taskId] });
236
+ currentDepth++;
237
+ }
238
+ }
239
+ debugLog(`TaskGraph: Resolved conflicts. Original ${waves.length} wave(s) → ${resolved.length} wave(s).`);
240
+ return resolved;
241
+ }
242
+ // ─── PM Self-Correction Prompt ───────────────────────────────────────
243
+ /**
244
+ * Generates the system prompt sent to the PM Agent when Kahn's algorithm
245
+ * detects a cycle, requesting it to fix its own task graph.
246
+ */
247
+ export function buildCycleCorrectionPrompt(cycleNodes) {
248
+ return (`[SYSTEM — TASK GRAPH VALIDATION FAILED]\n` +
249
+ `Your generated task graph contains a cyclic dependency:\n` +
250
+ `${cycleNodes.join('\n')}\n\n` +
251
+ `This is impossible to execute — tasks cannot depend on each other circularly.\n` +
252
+ `Please regenerate the task graph with corrected dependencies. Ensure that the ` +
253
+ `dependency chain is strictly acyclic (no task can transitively depend on itself).`);
254
+ }
@@ -34,6 +34,7 @@ export interface ProxyUsageMetadata {
34
34
  */
35
35
  export declare class ProxyClient {
36
36
  private readonly PROXY_URL;
37
+ private readonly EMBED_URL;
37
38
  /**
38
39
  * Generates text, thoughts, or function calls via the secure Gemini proxy URL.
39
40
  * Utilizes Server-Sent Events (SSE) to stream partial token responses back to the client.
@@ -60,4 +61,28 @@ export declare class ProxyClient {
60
61
  usageMetadata?: ProxyUsageMetadata;
61
62
  groundingMetadata?: any;
62
63
  }>;
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
+ }>;
63
88
  }
@@ -5,6 +5,7 @@ import { debugLog } from '../utils/logger.js';
5
5
  */
6
6
  export class ProxyClient {
7
7
  PROXY_URL = 'https://generatecontent-6obg3e4zwa-uc.a.run.app';
8
+ EMBED_URL = 'https://embedcontent-6obg3e4zwa-uc.a.run.app';
8
9
  /**
9
10
  * Generates text, thoughts, or function calls via the secure Gemini proxy URL.
10
11
  * Utilizes Server-Sent Events (SSE) to stream partial token responses back to the client.
@@ -141,4 +142,63 @@ export class ProxyClient {
141
142
  groundingMetadata,
142
143
  };
143
144
  }
145
+ /**
146
+ * Embeds one or more text chunks via the secure embedding proxy endpoint.
147
+ * Uses the same Firebase auth pattern as generateFunctionCallViaProxy, but
148
+ * targets a separate Cloud Function optimized for embedding generation.
149
+ *
150
+ * Unlike the generative endpoint, embedding responses are small and atomic,
151
+ * so no SSE streaming is required — a single JSON response is returned.
152
+ *
153
+ * @param idToken - The Firebase ID token for authorization.
154
+ * @param texts - Array of text strings to embed. Batched by caller (max ~25 per call).
155
+ * @param taskType - Embedding task type hint for optimal retrieval quality.
156
+ * - 'RETRIEVAL_DOCUMENT': Used when indexing source code chunks.
157
+ * - 'RETRIEVAL_QUERY': Used when embedding a user's semantic search query.
158
+ * @returns The embedding vectors and usage metadata from the proxy.
159
+ * @throws {Error} If authentication fails (401), credits are insufficient (402), or network errors occur.
160
+ */
161
+ async embedTextsViaProxy(idToken, texts, taskType = 'RETRIEVAL_DOCUMENT') {
162
+ const response = await fetch(this.EMBED_URL, {
163
+ method: 'POST',
164
+ headers: {
165
+ 'Content-Type': 'application/json',
166
+ 'X-Firebase-Auth': `Bearer ${idToken}`,
167
+ },
168
+ body: JSON.stringify({
169
+ contents: texts,
170
+ taskType,
171
+ }),
172
+ });
173
+ debugLog(`Embed Proxy Request complete. Status: ${response.status} ${response.statusText}`);
174
+ if (response.status === 401) {
175
+ let details = '';
176
+ try {
177
+ const text = await response.text();
178
+ try {
179
+ const errorData = JSON.parse(text);
180
+ details = errorData.details || errorData.error || text;
181
+ }
182
+ catch {
183
+ details = text;
184
+ }
185
+ }
186
+ catch {
187
+ details = 'Unknown error reading body';
188
+ }
189
+ throw new Error(`Authentication failed: ${details}. Please login again.`);
190
+ }
191
+ if (response.status === 402) {
192
+ throw new Error('Insufficient credits. Please visit minovativemind.dev to purchase more credits.');
193
+ }
194
+ if (!response.ok) {
195
+ const errorData = await response.json().catch(() => ({}));
196
+ throw new Error(`Embed proxy error ${response.status}: ${errorData.error || response.statusText}`);
197
+ }
198
+ const data = await response.json();
199
+ return {
200
+ embeddings: data.embeddings?.map((e) => e.values || e) || [],
201
+ usage: data.usage,
202
+ };
203
+ }
144
204
  }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Represents a single registered external workspace entry in the global registry.
3
+ */
4
+ export interface RegisteredWorkspace {
5
+ /** User-chosen short alias (e.g., "backend", "shared-lib"). Used as the `@alias/` prefix. */
6
+ alias: string;
7
+ /** Validated, normalized absolute path on disk. */
8
+ absolutePath: string;
9
+ /** Unix epoch timestamp (ms) when this workspace was registered. */
10
+ registeredAt: number;
11
+ }
12
+ /**
13
+ * Result of resolving an `@alias/relative/path` string against the workspace registry.
14
+ */
15
+ export interface ResolvedWorkspacePath {
16
+ /** The alias that was matched (e.g., "backend"). */
17
+ alias: string;
18
+ /** The absolute root directory of the matched registered workspace. */
19
+ workspaceRoot: string;
20
+ /** The relative path within that workspace (e.g., "src/routes.ts"). */
21
+ relativePath: string;
22
+ /** The fully resolved absolute path to the target file or directory. */
23
+ absolutePath: string;
24
+ }
25
+ /**
26
+ * Service that manages a global registry of external workspace roots.
27
+ *
28
+ * Workspaces are registered with short aliases (e.g., "backend") and referenced
29
+ * in file paths using the `@alias/path` prefix syntax. The registry is persisted
30
+ * globally at `~/.minovative-mind-cli/workspaces.json` so registrations carry
31
+ * across different primary workspaces.
32
+ *
33
+ * @remarks
34
+ * This is a singleton — use the exported `workspaceRegistry` instance.
35
+ * All filesystem access goes through this service to maintain the security
36
+ * boundary: the agent can only touch the primary workspace and explicitly
37
+ * registered secondary workspaces.
38
+ */
39
+ declare class WorkspaceRegistry {
40
+ /** In-memory map of alias → registered workspace. */
41
+ private workspaces;
42
+ /** Whether the registry has been loaded from disk. */
43
+ private initialized;
44
+ /**
45
+ * Initializes the registry by loading persisted workspace entries from disk.
46
+ * Safe to call multiple times — subsequent calls are no-ops.
47
+ */
48
+ init(): void;
49
+ /**
50
+ * Registers a new external workspace root with the given alias.
51
+ *
52
+ * @param alias - Short identifier for the workspace (e.g., "backend").
53
+ * Must be lowercase alphanumeric with hyphens/underscores, 1–30 chars.
54
+ * @param absolutePath - Absolute path to the workspace root directory.
55
+ * Must exist on disk and be a directory.
56
+ * @returns The created `RegisteredWorkspace`, or throws on validation failure.
57
+ * @throws Error if the alias is invalid, the path doesn't exist, or the alias is already taken.
58
+ */
59
+ register(alias: string, absolutePath: string): RegisteredWorkspace;
60
+ /**
61
+ * Removes a registered workspace by alias.
62
+ *
63
+ * @param alias - The alias to unregister.
64
+ * @returns `true` if the workspace was found and removed, `false` otherwise.
65
+ */
66
+ unregister(alias: string): boolean;
67
+ /**
68
+ * Returns all registered workspaces as an array, sorted by alias.
69
+ */
70
+ list(): RegisteredWorkspace[];
71
+ /**
72
+ * Returns the number of registered workspaces.
73
+ */
74
+ get size(): number;
75
+ /**
76
+ * Checks whether any external workspaces are registered.
77
+ */
78
+ hasWorkspaces(): boolean;
79
+ /**
80
+ * Looks up a workspace by alias.
81
+ *
82
+ * @param alias - The alias to find (without the `@` prefix).
83
+ * @returns The registered workspace, or `undefined` if not found.
84
+ */
85
+ get(alias: string): RegisteredWorkspace | undefined;
86
+ /**
87
+ * Returns all workspace roots (primary + registered secondaries).
88
+ *
89
+ * @param primaryRoot - The primary workspace root (from `process.cwd()`).
90
+ * @returns An array of `{ alias: string | null, root: string }` entries.
91
+ * The primary workspace has `alias: null`.
92
+ */
93
+ getAllRoots(primaryRoot: string): Array<{
94
+ alias: string | null;
95
+ root: string;
96
+ }>;
97
+ /**
98
+ * Resolves an `@alias/relative/path` string into its constituent parts.
99
+ *
100
+ * @param filePath - A file path that may or may not start with `@alias/`.
101
+ * @returns A `ResolvedWorkspacePath` if the path has a valid `@alias/` prefix
102
+ * and the alias is registered, or `null` if the path is a standard
103
+ * workspace-relative path (no `@` prefix or unrecognized alias).
104
+ * @throws Error if the path has an `@` prefix but the alias is not registered.
105
+ */
106
+ resolve(filePath: string): ResolvedWorkspacePath | null;
107
+ /**
108
+ * Checks if a given file path uses the `@alias/` prefix syntax.
109
+ *
110
+ * @param filePath - The path to check.
111
+ * @returns `true` if the path starts with `@`.
112
+ */
113
+ isAliasedPath(filePath: string): boolean;
114
+ /**
115
+ * Builds a formatted summary of all registered workspaces for system prompt injection.
116
+ *
117
+ * @returns A human-readable string listing all aliases and their paths,
118
+ * or an empty string if no workspaces are registered.
119
+ */
120
+ buildPromptSummary(): string;
121
+ /**
122
+ * Loads the workspace registry from the global config file.
123
+ * Silently handles missing or corrupted files.
124
+ */
125
+ private loadFromDisk;
126
+ /**
127
+ * Persists the current workspace registry to the global config file.
128
+ * Creates the config directory if it doesn't exist.
129
+ */
130
+ private saveToDisk;
131
+ }
132
+ /**
133
+ * Singleton instance of the WorkspaceRegistry service.
134
+ * Initialize with `workspaceRegistry.init()` during CLI startup.
135
+ */
136
+ export declare const workspaceRegistry: WorkspaceRegistry;
137
+ export {};