minovative-mind-cli 2.11.3 → 2.11.5
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 +7 -6
- package/dist/services/agent.js +21 -3
- package/dist/services/ai.d.ts +1 -1
- package/dist/services/ai.js +45 -12
- package/dist/services/investigationComplexity.d.ts +39 -13
- package/dist/services/investigationComplexity.js +325 -46
- package/dist/services/orchestration/investigationAgent.js +1 -1
- package/dist/services/orchestration/investigationOrchestrator.js +5 -0
- package/dist/services/orchestration/orchestrator.js +16 -4
- package/dist/services/orchestration/scopedTools.d.ts +27 -50
- package/dist/services/orchestration/scopedTools.js +60 -18
- package/dist/services/proxyClient.js +132 -47
- package/dist/services/workspaceRegistry.d.ts +81 -8
- package/dist/services/workspaceRegistry.js +222 -34
- package/dist/utils/analysisRunner.js +2 -1
- package/dist/utils/config.d.ts +10 -0
- package/dist/utils/config.js +10 -0
- package/dist/utils/pathSecurity.d.ts +56 -14
- package/dist/utils/pathSecurity.js +120 -39
- package/dist/utils/systemPrompts.d.ts +6 -6
- package/dist/utils/systemPrompts.js +43 -22
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { executeTool, getToolDeclarations as getBaseToolDeclarations } from '../agent-tools.js';
|
|
3
3
|
import { MessageBus } from './messageBus.js';
|
|
4
|
+
import { resolveAndValidateMultiWorkspacePath } from '../../utils/pathSecurity.js';
|
|
4
5
|
import { debugLog } from '../../utils/logger.js';
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Resolves a file path to its canonical absolute path for lock registry keying,
|
|
8
|
+
* accounting for multi-workspace alias resolution and default primary sub-path auto-focusing.
|
|
9
|
+
*
|
|
10
|
+
* @param workspaceRoot - The base primary workspace root directory
|
|
11
|
+
* @param filePath - The relative or aliased file path
|
|
12
|
+
* @param options - Optional sub-path override configuration
|
|
13
|
+
* @returns Absolute canonical file path
|
|
14
|
+
*/
|
|
15
|
+
export function resolveCanonicalLockPath(workspaceRoot, filePath, options) {
|
|
16
|
+
try {
|
|
17
|
+
const resolved = resolveAndValidateMultiWorkspacePath(workspaceRoot, filePath, options);
|
|
18
|
+
return resolved.absolutePath;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return path.isAbsolute(filePath) ? filePath : path.resolve(workspaceRoot, filePath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns Gemini tool declarations available to sub-agents during orchestration.
|
|
26
|
+
* In addition to standard agent tools (read/write/search/command/etc.), includes
|
|
27
|
+
* inter-agent communication tools (post_message, read_messages).
|
|
8
28
|
*/
|
|
9
29
|
export function getScopedToolDeclarations() {
|
|
10
|
-
|
|
11
|
-
|
|
30
|
+
const baseTools = getBaseToolDeclarations({ isExecutionAgent: true });
|
|
31
|
+
const orchestrationTools = [
|
|
12
32
|
{
|
|
13
33
|
name: 'post_message',
|
|
14
34
|
description: 'Post a semantic message to the orchestration bus to coordinate with other agents.',
|
|
@@ -23,15 +43,15 @@ export function getScopedToolDeclarations() {
|
|
|
23
43
|
type: 'STRING',
|
|
24
44
|
description: 'The semantic intent or message content',
|
|
25
45
|
},
|
|
26
|
-
toAgent: {
|
|
27
|
-
type: 'STRING',
|
|
28
|
-
description: 'Optional target agent ID (required for "request")',
|
|
29
|
-
},
|
|
30
46
|
affectedFiles: {
|
|
31
47
|
type: 'ARRAY',
|
|
32
48
|
items: { type: 'STRING' },
|
|
33
49
|
description: 'Files involved (required for "discovery")',
|
|
34
50
|
},
|
|
51
|
+
toAgent: {
|
|
52
|
+
type: 'STRING',
|
|
53
|
+
description: 'Optional target agent ID (required for "request")',
|
|
54
|
+
},
|
|
35
55
|
},
|
|
36
56
|
required: ['type', 'content'],
|
|
37
57
|
},
|
|
@@ -45,16 +65,23 @@ export function getScopedToolDeclarations() {
|
|
|
45
65
|
},
|
|
46
66
|
},
|
|
47
67
|
];
|
|
68
|
+
return [...baseTools, ...orchestrationTools];
|
|
48
69
|
}
|
|
49
70
|
/**
|
|
50
|
-
*
|
|
71
|
+
* Executes a tool within the sub-agent execution boundary.
|
|
72
|
+
* Handles concurrency locking for file-mutating operations, sends progress
|
|
73
|
+
* notifications to the orchestrator, and logs activity to the message bus.
|
|
51
74
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
75
|
+
* @param name - Tool function name
|
|
76
|
+
* @param args - Tool arguments object
|
|
77
|
+
* @param workspaceRoot - Primary workspace root directory
|
|
78
|
+
* @param agentId - Unique ID of the executing agent
|
|
79
|
+
* @param bus - Shared message bus instance
|
|
80
|
+
* @param locks - Shared file lock registry instance
|
|
81
|
+
* @param onProgress - Callback to notify parent of sub-agent progress
|
|
82
|
+
* @param options - Optional sub-path auto-focus and override configuration
|
|
56
83
|
*/
|
|
57
|
-
export async function executeScopedTool(name, args, workspaceRoot, agentId, bus, locks, onProgress) {
|
|
84
|
+
export async function executeScopedTool(name, args, workspaceRoot, agentId, bus, locks, onProgress, options) {
|
|
58
85
|
// Update heartbeat so the orchestrator knows we are making progress
|
|
59
86
|
onProgress();
|
|
60
87
|
const timestamp = Date.now();
|
|
@@ -97,10 +124,25 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
|
|
|
97
124
|
let lockedFile = null;
|
|
98
125
|
let diffContext = null;
|
|
99
126
|
if (name === 'write_file' || name === 'modify_file' || name === 'delete_file') {
|
|
100
|
-
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
127
|
+
const rawPath = args.filePath;
|
|
128
|
+
if (rawPath) {
|
|
129
|
+
lockedFile = resolveCanonicalLockPath(workspaceRoot, rawPath, options);
|
|
130
|
+
debugLog(`Agent "${agentId}" requesting lock for "${lockedFile}" (raw: "${rawPath}", tool: ${name})`);
|
|
131
|
+
onProgress(`waiting for lock on ${path.basename(lockedFile)}...`);
|
|
132
|
+
const lockRes = await locks.acquire(lockedFile, agentId);
|
|
133
|
+
onProgress(`acquired lock, executing...`);
|
|
134
|
+
diffContext = lockRes.previousDiff;
|
|
135
|
+
if (lockRes.forceReleased) {
|
|
136
|
+
debugLog(`Agent "${agentId}" got forced lock on "${lockedFile}" (previous owner stalled)`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (name === 'rename_file') {
|
|
141
|
+
const rawPath = args.sourcePath;
|
|
142
|
+
if (rawPath) {
|
|
143
|
+
lockedFile = resolveCanonicalLockPath(workspaceRoot, rawPath, options);
|
|
144
|
+
debugLog(`Agent "${agentId}" requesting lock for "${lockedFile}" (raw: "${rawPath}", tool: ${name})`);
|
|
145
|
+
onProgress(`waiting for lock on ${path.basename(lockedFile)}...`);
|
|
104
146
|
const lockRes = await locks.acquire(lockedFile, agentId);
|
|
105
147
|
onProgress(`acquired lock, executing...`);
|
|
106
148
|
diffContext = lockRes.previousDiff;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { debugLog } from '../utils/logger.js';
|
|
1
|
+
import { debugLog, isDebugOn } from '../utils/logger.js';
|
|
2
|
+
import { GEMINI_MODELS } from '../utils/config.js';
|
|
2
3
|
import { getMetricCollector } from './metrics.js';
|
|
3
4
|
/**
|
|
4
5
|
* ============================================================================
|
|
@@ -98,6 +99,7 @@ export class ProxyClient {
|
|
|
98
99
|
const BASE_DELAY_MS = 2000;
|
|
99
100
|
const MAX_DELAY_MS = 30000;
|
|
100
101
|
let attempt = 0;
|
|
102
|
+
let activeModel = modelName;
|
|
101
103
|
retryLoop: while (true) {
|
|
102
104
|
if (abortSignal?.aborted) {
|
|
103
105
|
const err = new Error('Operation aborted');
|
|
@@ -111,7 +113,7 @@ export class ProxyClient {
|
|
|
111
113
|
'X-Firebase-Auth': `Bearer ${idToken}`,
|
|
112
114
|
},
|
|
113
115
|
body: JSON.stringify({
|
|
114
|
-
model:
|
|
116
|
+
model: activeModel,
|
|
115
117
|
contents,
|
|
116
118
|
tools,
|
|
117
119
|
toolConfig,
|
|
@@ -120,25 +122,39 @@ export class ProxyClient {
|
|
|
120
122
|
}),
|
|
121
123
|
signal: abortSignal,
|
|
122
124
|
});
|
|
123
|
-
debugLog(`Proxy Request to ${
|
|
124
|
-
if (
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
debugLog(`Proxy Request to ${activeModel} complete. Status: ${response.status} ${response.statusText}`);
|
|
126
|
+
if (response.status === 429 || response.status === 503 || response.status === 502 || response.status === 500 || response.status === 504) {
|
|
127
|
+
if (attempt < MAX_RETRIES) {
|
|
128
|
+
if (abortSignal?.aborted) {
|
|
129
|
+
const err = new Error('Operation aborted');
|
|
130
|
+
err.name = 'AbortError';
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
134
|
+
const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
|
|
135
|
+
if (isDebugOn()) {
|
|
136
|
+
process.stdout.write('\n');
|
|
137
|
+
console.warn(`Server error or rate limit hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
138
|
+
}
|
|
139
|
+
await delay(delayTime, abortSignal);
|
|
140
|
+
if (abortSignal?.aborted) {
|
|
141
|
+
const err = new Error('Operation aborted');
|
|
142
|
+
err.name = 'AbortError';
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
attempt++;
|
|
146
|
+
continue;
|
|
129
147
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
else if (activeModel !== GEMINI_MODELS.FLASH_LITE) {
|
|
149
|
+
debugLog(`Rate limit retries exhausted (${MAX_RETRIES}/${MAX_RETRIES}) for ${activeModel}. Automatically falling back to ${GEMINI_MODELS.FLASH_LITE}...`);
|
|
150
|
+
if (isDebugOn()) {
|
|
151
|
+
process.stdout.write('\n');
|
|
152
|
+
console.warn(`Rate limit retries exhausted on ${activeModel}. Automatically switching to Flash-Lite (${GEMINI_MODELS.FLASH_LITE}) to complete request...`);
|
|
153
|
+
}
|
|
154
|
+
activeModel = GEMINI_MODELS.FLASH_LITE;
|
|
155
|
+
attempt = 0;
|
|
156
|
+
continue retryLoop;
|
|
139
157
|
}
|
|
140
|
-
attempt++;
|
|
141
|
-
continue;
|
|
142
158
|
}
|
|
143
159
|
if (response.status === 401) {
|
|
144
160
|
let details = '';
|
|
@@ -227,8 +243,8 @@ export class ProxyClient {
|
|
|
227
243
|
if (data.usage.remainingBalance !== undefined) {
|
|
228
244
|
globalSessionAccumulatedUsage.remainingBalance = data.usage.remainingBalance;
|
|
229
245
|
}
|
|
230
|
-
globalSessionAccumulatedUsage.modelsUsed[
|
|
231
|
-
(globalSessionAccumulatedUsage.modelsUsed[
|
|
246
|
+
globalSessionAccumulatedUsage.modelsUsed[activeModel] =
|
|
247
|
+
(globalSessionAccumulatedUsage.modelsUsed[activeModel] || 0) + 1;
|
|
232
248
|
}
|
|
233
249
|
if (data.groundingMetadata) {
|
|
234
250
|
groundingMetadata = data.groundingMetadata;
|
|
@@ -264,8 +280,10 @@ export class ProxyClient {
|
|
|
264
280
|
if (attempt < MAX_RETRIES) {
|
|
265
281
|
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
266
282
|
const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
|
|
267
|
-
|
|
268
|
-
|
|
283
|
+
if (isDebugOn()) {
|
|
284
|
+
process.stdout.write('\n');
|
|
285
|
+
console.warn(`Server error or rate limit hit during stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
286
|
+
}
|
|
269
287
|
await delay(delayTime, abortSignal);
|
|
270
288
|
if (abortSignal?.aborted) {
|
|
271
289
|
const err = new Error('Operation aborted');
|
|
@@ -275,6 +293,16 @@ export class ProxyClient {
|
|
|
275
293
|
attempt++;
|
|
276
294
|
continue retryLoop;
|
|
277
295
|
}
|
|
296
|
+
else if (activeModel !== GEMINI_MODELS.FLASH_LITE) {
|
|
297
|
+
debugLog(`Stream rate limit retries exhausted (${MAX_RETRIES}/${MAX_RETRIES}) for ${activeModel}. Automatically falling back to ${GEMINI_MODELS.FLASH_LITE}...`);
|
|
298
|
+
if (isDebugOn()) {
|
|
299
|
+
process.stdout.write('\n');
|
|
300
|
+
console.warn(`Stream rate limit exhausted on ${activeModel}. Automatically switching to Flash-Lite (${GEMINI_MODELS.FLASH_LITE}) to complete request...`);
|
|
301
|
+
}
|
|
302
|
+
activeModel = GEMINI_MODELS.FLASH_LITE;
|
|
303
|
+
attempt = 0;
|
|
304
|
+
continue retryLoop;
|
|
305
|
+
}
|
|
278
306
|
}
|
|
279
307
|
throw streamError;
|
|
280
308
|
}
|
|
@@ -297,8 +325,6 @@ export class ProxyClient {
|
|
|
297
325
|
*/
|
|
298
326
|
async generateViaBYOK(apiKey, modelName, contents, tools, toolConfig, systemInstruction, generationConfig, streamCallbacks, abortSignal) {
|
|
299
327
|
const isStreaming = Boolean(streamCallbacks?.onChunk);
|
|
300
|
-
const endpoint = isStreaming ? 'streamGenerateContent?alt=sse&key=' : 'generateContent?key=';
|
|
301
|
-
const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(modelName)}:${endpoint}${encodeURIComponent(apiKey)}`;
|
|
302
328
|
const formattedSystemInstruction = typeof systemInstruction === 'string' ? { parts: [{ text: systemInstruction }] } : systemInstruction;
|
|
303
329
|
const payload = { contents };
|
|
304
330
|
if (tools && tools.length > 0)
|
|
@@ -313,42 +339,58 @@ export class ProxyClient {
|
|
|
313
339
|
const BASE_DELAY_MS = 2000;
|
|
314
340
|
const MAX_DELAY_MS = 30000;
|
|
315
341
|
let attempt = 0;
|
|
316
|
-
|
|
342
|
+
let activeModel = modelName;
|
|
343
|
+
retryLoop: while (true) {
|
|
317
344
|
if (abortSignal?.aborted) {
|
|
318
345
|
const err = new Error('Operation aborted');
|
|
319
346
|
err.name = 'AbortError';
|
|
320
347
|
throw err;
|
|
321
348
|
}
|
|
349
|
+
const endpoint = isStreaming ? 'streamGenerateContent?alt=sse&key=' : 'generateContent?key=';
|
|
350
|
+
const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(activeModel)}:${endpoint}${encodeURIComponent(apiKey)}`;
|
|
322
351
|
const response = await fetch(url, {
|
|
323
352
|
method: 'POST',
|
|
324
353
|
headers: { 'Content-Type': 'application/json' },
|
|
325
354
|
body: JSON.stringify(payload),
|
|
326
355
|
signal: abortSignal,
|
|
327
356
|
});
|
|
328
|
-
debugLog(`BYOK Request to ${
|
|
329
|
-
if (
|
|
357
|
+
debugLog(`BYOK Request to ${activeModel} complete. Status: ${response.status} ${response.statusText}`);
|
|
358
|
+
if (response.status === 429 ||
|
|
330
359
|
response.status === 503 ||
|
|
331
360
|
response.status === 502 ||
|
|
332
361
|
response.status === 500 ||
|
|
333
|
-
response.status === 504)
|
|
334
|
-
attempt < MAX_RETRIES) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
362
|
+
response.status === 504) {
|
|
363
|
+
if (attempt < MAX_RETRIES) {
|
|
364
|
+
if (abortSignal?.aborted) {
|
|
365
|
+
const err = new Error('Operation aborted');
|
|
366
|
+
err.name = 'AbortError';
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
369
|
+
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
370
|
+
const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
|
|
371
|
+
if (isDebugOn()) {
|
|
372
|
+
process.stdout.write('\n');
|
|
373
|
+
console.warn(`Server error or rate limit hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
374
|
+
}
|
|
375
|
+
await delay(delayTime, abortSignal);
|
|
376
|
+
if (abortSignal?.aborted) {
|
|
377
|
+
const err = new Error('Operation aborted');
|
|
378
|
+
err.name = 'AbortError';
|
|
379
|
+
throw err;
|
|
380
|
+
}
|
|
381
|
+
attempt++;
|
|
382
|
+
continue;
|
|
339
383
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
384
|
+
else if (activeModel !== GEMINI_MODELS.FLASH_LITE) {
|
|
385
|
+
debugLog(`BYOK rate limit retries exhausted (${MAX_RETRIES}/${MAX_RETRIES}) for ${activeModel}. Automatically falling back to ${GEMINI_MODELS.FLASH_LITE}...`);
|
|
386
|
+
if (isDebugOn()) {
|
|
387
|
+
process.stdout.write('\n');
|
|
388
|
+
console.warn(`BYOK rate limit retries exhausted on ${activeModel}. Automatically switching to Flash-Lite (${GEMINI_MODELS.FLASH_LITE}) to complete request...`);
|
|
389
|
+
}
|
|
390
|
+
activeModel = GEMINI_MODELS.FLASH_LITE;
|
|
391
|
+
attempt = 0;
|
|
392
|
+
continue retryLoop;
|
|
349
393
|
}
|
|
350
|
-
attempt++;
|
|
351
|
-
continue;
|
|
352
394
|
}
|
|
353
395
|
if (!response.ok) {
|
|
354
396
|
let errorData = {};
|
|
@@ -427,7 +469,7 @@ export class ProxyClient {
|
|
|
427
469
|
creditsUsed: 0,
|
|
428
470
|
remainingBalance: 0,
|
|
429
471
|
};
|
|
430
|
-
accumulateTurnUsage(data.usageMetadata,
|
|
472
|
+
accumulateTurnUsage(data.usageMetadata, activeModel);
|
|
431
473
|
}
|
|
432
474
|
}
|
|
433
475
|
catch (parseError) {
|
|
@@ -436,6 +478,49 @@ export class ProxyClient {
|
|
|
436
478
|
}
|
|
437
479
|
}
|
|
438
480
|
}
|
|
481
|
+
catch (streamError) {
|
|
482
|
+
if (abortSignal?.aborted || streamError.name === 'AbortError' || streamError.message?.includes('abort')) {
|
|
483
|
+
const err = new Error('Operation aborted');
|
|
484
|
+
err.name = 'AbortError';
|
|
485
|
+
throw err;
|
|
486
|
+
}
|
|
487
|
+
if (streamError.message?.includes('429') ||
|
|
488
|
+
streamError.message?.includes('502') ||
|
|
489
|
+
streamError.message?.includes('503') ||
|
|
490
|
+
streamError.message?.includes('500') ||
|
|
491
|
+
streamError.message?.includes('504') ||
|
|
492
|
+
streamError.message?.includes('Bad Gateway') ||
|
|
493
|
+
streamError.message?.includes('RESOURCE_EXHAUSTED') ||
|
|
494
|
+
streamError.message?.includes('Too Many Requests')) {
|
|
495
|
+
if (attempt < MAX_RETRIES) {
|
|
496
|
+
const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
|
|
497
|
+
const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
|
|
498
|
+
if (isDebugOn()) {
|
|
499
|
+
process.stdout.write('\n');
|
|
500
|
+
console.warn(`Server error or rate limit hit during BYOK stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
|
|
501
|
+
}
|
|
502
|
+
await delay(delayTime, abortSignal);
|
|
503
|
+
if (abortSignal?.aborted) {
|
|
504
|
+
const err = new Error('Operation aborted');
|
|
505
|
+
err.name = 'AbortError';
|
|
506
|
+
throw err;
|
|
507
|
+
}
|
|
508
|
+
attempt++;
|
|
509
|
+
continue retryLoop;
|
|
510
|
+
}
|
|
511
|
+
else if (activeModel !== GEMINI_MODELS.FLASH_LITE) {
|
|
512
|
+
debugLog(`BYOK stream rate limit retries exhausted (${MAX_RETRIES}/${MAX_RETRIES}) for ${activeModel}. Automatically falling back to ${GEMINI_MODELS.FLASH_LITE}...`);
|
|
513
|
+
if (isDebugOn()) {
|
|
514
|
+
process.stdout.write('\n');
|
|
515
|
+
console.warn(`BYOK stream rate limit exhausted on ${activeModel}. Automatically switching to Flash-Lite (${GEMINI_MODELS.FLASH_LITE}) to complete request...`);
|
|
516
|
+
}
|
|
517
|
+
activeModel = GEMINI_MODELS.FLASH_LITE;
|
|
518
|
+
attempt = 0;
|
|
519
|
+
continue retryLoop;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
throw streamError;
|
|
523
|
+
}
|
|
439
524
|
finally {
|
|
440
525
|
reader.releaseLock();
|
|
441
526
|
}
|
|
@@ -469,7 +554,7 @@ export class ProxyClient {
|
|
|
469
554
|
creditsUsed: 0,
|
|
470
555
|
remainingBalance: 0,
|
|
471
556
|
};
|
|
472
|
-
accumulateTurnUsage(data.usageMetadata,
|
|
557
|
+
accumulateTurnUsage(data.usageMetadata, activeModel);
|
|
473
558
|
}
|
|
474
559
|
}
|
|
475
560
|
return {
|
|
@@ -24,15 +24,27 @@ export interface Profile {
|
|
|
24
24
|
* Result of resolving an `@alias/relative/path` string against the workspace registry.
|
|
25
25
|
*/
|
|
26
26
|
export interface ResolvedWorkspacePath {
|
|
27
|
-
/** The alias that was matched (e.g., "backend"). */
|
|
28
|
-
alias: string;
|
|
29
|
-
/** The absolute root directory of the matched registered workspace. */
|
|
27
|
+
/** The alias that was matched (e.g., "backend"), or null for primary workspace. */
|
|
28
|
+
alias: string | null;
|
|
29
|
+
/** The absolute root directory of the matched registered workspace or primary workspace. */
|
|
30
30
|
workspaceRoot: string;
|
|
31
31
|
/** The relative path within that workspace (e.g., "src/routes.ts"). */
|
|
32
32
|
relativePath: string;
|
|
33
33
|
/** The fully resolved absolute path to the target file or directory. */
|
|
34
34
|
absolutePath: string;
|
|
35
|
+
/** Whether the path was resolved via primary sub-path auto-focusing. */
|
|
36
|
+
isAutoFocused?: boolean;
|
|
35
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns the global configuration directory for Minovative Mind CLI.
|
|
40
|
+
* During automated tests, returns an isolated temporary directory to prevent test runs
|
|
41
|
+
* from modifying the developer's live workspace configurations.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getGlobalConfigDir(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Returns the active path to the workspace registry JSON file.
|
|
46
|
+
*/
|
|
47
|
+
export declare function getRegistryFile(): string;
|
|
36
48
|
/**
|
|
37
49
|
* Service that manages a global registry of external workspace roots.
|
|
38
50
|
*
|
|
@@ -54,11 +66,16 @@ declare class WorkspaceRegistry {
|
|
|
54
66
|
private profiles;
|
|
55
67
|
/** Whether the registry has been loaded from disk. */
|
|
56
68
|
private initialized;
|
|
69
|
+
private primarySubPath;
|
|
57
70
|
/**
|
|
58
71
|
* Initializes the registry by loading persisted workspace entries from disk.
|
|
59
72
|
* Safe to call multiple times — subsequent calls are no-ops.
|
|
60
73
|
*/
|
|
61
74
|
init(): void;
|
|
75
|
+
/**
|
|
76
|
+
* Internal guard to guarantee the registry is loaded before any read or write operation.
|
|
77
|
+
*/
|
|
78
|
+
private ensureInitialized;
|
|
62
79
|
/**
|
|
63
80
|
* Registers a new external workspace root with the given alias under a profile.
|
|
64
81
|
*
|
|
@@ -124,15 +141,71 @@ declare class WorkspaceRegistry {
|
|
|
124
141
|
root: string;
|
|
125
142
|
}>;
|
|
126
143
|
/**
|
|
127
|
-
*
|
|
144
|
+
* Sets the active primary sub-path for auto-focusing relative file queries.
|
|
145
|
+
*
|
|
146
|
+
* @param subPath - The relative directory path within the primary workspace (e.g., "src/services"), or null to clear.
|
|
147
|
+
*/
|
|
148
|
+
setPrimarySubPath(subPath: string | null): void;
|
|
149
|
+
/**
|
|
150
|
+
* Returns the currently active primary sub-path, or null if none is set.
|
|
151
|
+
*/
|
|
152
|
+
getPrimarySubPath(): string | null;
|
|
153
|
+
/**
|
|
154
|
+
* Returns whether a primary sub-path is currently active.
|
|
155
|
+
*/
|
|
156
|
+
hasPrimarySubPath(): boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Clears the active primary sub-path.
|
|
159
|
+
*/
|
|
160
|
+
clearPrimarySubPath(): void;
|
|
161
|
+
/**
|
|
162
|
+
* Gets the focused root directory for the given primary workspace root.
|
|
163
|
+
* If a primary sub-path is active, returns the resolved sub-path directory;
|
|
164
|
+
* otherwise returns the primary root.
|
|
165
|
+
*
|
|
166
|
+
* @param primaryRoot - The base primary workspace root directory.
|
|
167
|
+
*/
|
|
168
|
+
getFocusedRoot(primaryRoot: string): string;
|
|
169
|
+
/**
|
|
170
|
+
* Checks whether a target path is securely contained within a given boundary root.
|
|
171
|
+
*
|
|
172
|
+
* @param targetPath - The absolute or relative target path.
|
|
173
|
+
* @param boundaryRoot - The boundary root directory.
|
|
174
|
+
*/
|
|
175
|
+
isPathWithinBoundary(targetPath: string, boundaryRoot: string): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Finds a registered workspace that contains the given file or directory path.
|
|
178
|
+
*
|
|
179
|
+
* @param targetPath - The file or directory path to check.
|
|
180
|
+
* @returns The matching `RegisteredWorkspace` or null if not found.
|
|
181
|
+
*/
|
|
182
|
+
findWorkspaceForPath(targetPath: string): RegisteredWorkspace | null;
|
|
183
|
+
/**
|
|
184
|
+
* Checks whether a path resides inside the primary workspace or any registered workspace.
|
|
185
|
+
*
|
|
186
|
+
* @param targetPath - The file or directory path to inspect.
|
|
187
|
+
* @param primaryRoot - Optional primary workspace root.
|
|
188
|
+
*/
|
|
189
|
+
isInsideRegisteredWorkspace(targetPath: string, primaryRoot?: string): boolean;
|
|
190
|
+
/**
|
|
191
|
+
* Resolves an `@alias/relative/path` string or primary workspace path into constituent parts.
|
|
128
192
|
*
|
|
129
193
|
* @param filePath - A file path that may or may not start with `@alias/`.
|
|
130
|
-
* @
|
|
131
|
-
*
|
|
132
|
-
* workspace-relative path (no `@` prefix or unrecognized alias).
|
|
194
|
+
* @param options - Optional resolution options including primaryRoot and autoFocusSubPath.
|
|
195
|
+
* @returns A `ResolvedWorkspacePath` if resolved, or `null` if the path cannot be resolved.
|
|
133
196
|
* @throws Error if the path has an `@` prefix but the alias is not registered.
|
|
134
197
|
*/
|
|
135
|
-
resolve(filePath: string
|
|
198
|
+
resolve(filePath: string, options?: {
|
|
199
|
+
primaryRoot?: string;
|
|
200
|
+
autoFocusSubPath?: boolean;
|
|
201
|
+
}): ResolvedWorkspacePath | null;
|
|
202
|
+
/**
|
|
203
|
+
* Resolves a relative path against the primary root with automatic sub-path focusing.
|
|
204
|
+
*
|
|
205
|
+
* @param primaryRoot - The base primary workspace root directory.
|
|
206
|
+
* @param relativePath - The target relative path to resolve.
|
|
207
|
+
*/
|
|
208
|
+
resolveWithAutoFocus(primaryRoot: string, relativePath: string): ResolvedWorkspacePath;
|
|
136
209
|
/**
|
|
137
210
|
* Checks if a given file path uses the `@alias/` prefix syntax.
|
|
138
211
|
*
|