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
|
@@ -11,91 +11,370 @@
|
|
|
11
11
|
* has already determined that context gathering is needed (SEARCH).
|
|
12
12
|
*/
|
|
13
13
|
import { createInvestigationComplexitySession } from './ai.js';
|
|
14
|
+
import { workspaceRegistry } from './workspaceRegistry.js';
|
|
14
15
|
import { debugLog } from '../utils/logger.js';
|
|
15
|
-
// ───
|
|
16
|
+
// ─── Helper Functions ────────────────────────────────────────────────
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
+
* Checks whether the user's prompt contains an explicit path or workspace override
|
|
19
|
+
* that supersedes default primary sub-path auto-focusing.
|
|
18
20
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
21
|
+
* @param userRequest - The prompt text from the user
|
|
22
|
+
* @param activeSubPath - The currently active primary sub-path (if any)
|
|
23
|
+
* @returns The detected override identifier or null if no override exists
|
|
24
|
+
*/
|
|
25
|
+
export function detectSubPathOverride(userRequest, activeSubPath) {
|
|
26
|
+
if (!userRequest)
|
|
27
|
+
return null;
|
|
28
|
+
// Check for external workspace alias syntax (e.g. "@backend/...", "@calc", etc.)
|
|
29
|
+
const aliasMatch = userRequest.match(/@([a-zA-Z0-9_\-]+)(\/|\s|$)/);
|
|
30
|
+
if (aliasMatch) {
|
|
31
|
+
return `@${aliasMatch[1]}`;
|
|
32
|
+
}
|
|
33
|
+
// Check for explicit root-level or full-codebase directives
|
|
34
|
+
const lower = userRequest.toLowerCase();
|
|
35
|
+
if (lower.includes('whole project') ||
|
|
36
|
+
lower.includes('entire codebase') ||
|
|
37
|
+
lower.includes('full codebase') ||
|
|
38
|
+
lower.includes('across workspaces') ||
|
|
39
|
+
lower.includes('all workspaces') ||
|
|
40
|
+
lower.includes('root directory') ||
|
|
41
|
+
lower.includes('from root') ||
|
|
42
|
+
lower.includes('@root') ||
|
|
43
|
+
lower.includes('entire project') ||
|
|
44
|
+
lower.includes('entire repo') ||
|
|
45
|
+
lower.includes('all packages') ||
|
|
46
|
+
lower.includes('all modules') ||
|
|
47
|
+
lower.includes('across the project') ||
|
|
48
|
+
lower.includes('across the codebase') ||
|
|
49
|
+
lower.includes('every workspace')) {
|
|
50
|
+
return 'root';
|
|
51
|
+
}
|
|
52
|
+
// If an active sub-path is present (e.g. "src" or "packages/core"), check if root files outside it are targeted
|
|
53
|
+
if (activeSubPath) {
|
|
54
|
+
const rootFiles = [
|
|
55
|
+
// Package Managers & Dependencies
|
|
56
|
+
'package.json',
|
|
57
|
+
'package-lock.json',
|
|
58
|
+
'yarn.lock',
|
|
59
|
+
'pnpm-lock.yaml',
|
|
60
|
+
'pnpm-workspace.yaml',
|
|
61
|
+
'bun.lockb',
|
|
62
|
+
'bunfig.toml',
|
|
63
|
+
'cargo.toml',
|
|
64
|
+
'cargo.lock',
|
|
65
|
+
'go.mod',
|
|
66
|
+
'go.sum',
|
|
67
|
+
'go.work',
|
|
68
|
+
'pyproject.toml',
|
|
69
|
+
'requirements.txt',
|
|
70
|
+
'requirements-dev.txt',
|
|
71
|
+
'pipfile',
|
|
72
|
+
'pipfile.lock',
|
|
73
|
+
'setup.py',
|
|
74
|
+
'setup.cfg',
|
|
75
|
+
'poetry.lock',
|
|
76
|
+
'gemfile',
|
|
77
|
+
'gemfile.lock',
|
|
78
|
+
'composer.json',
|
|
79
|
+
'composer.lock',
|
|
80
|
+
'pom.xml',
|
|
81
|
+
'build.gradle',
|
|
82
|
+
'build.gradle.kts',
|
|
83
|
+
'settings.gradle',
|
|
84
|
+
'settings.gradle.kts',
|
|
85
|
+
'package.swift',
|
|
86
|
+
// Compilers, Bundlers & Frameworks
|
|
87
|
+
'tsconfig.json',
|
|
88
|
+
'jsconfig.json',
|
|
89
|
+
'next.config.ts',
|
|
90
|
+
'next.config.js',
|
|
91
|
+
'next.config.mjs',
|
|
92
|
+
'vite.config.ts',
|
|
93
|
+
'vite.config.js',
|
|
94
|
+
'vite.config.mjs',
|
|
95
|
+
'webpack.config.js',
|
|
96
|
+
'webpack.config.ts',
|
|
97
|
+
'rollup.config.js',
|
|
98
|
+
'rollup.config.ts',
|
|
99
|
+
'turbo.json',
|
|
100
|
+
'lerna.json',
|
|
101
|
+
'nx.json',
|
|
102
|
+
'babel.config.js',
|
|
103
|
+
'babel.config.json',
|
|
104
|
+
'.babelrc',
|
|
105
|
+
'postcss.config.js',
|
|
106
|
+
'tailwind.config.js',
|
|
107
|
+
'tailwind.config.ts',
|
|
108
|
+
'tailwind.config.mjs',
|
|
109
|
+
// Testing & Quality / Linters
|
|
110
|
+
'vitest.config.ts',
|
|
111
|
+
'vitest.config.mts',
|
|
112
|
+
'vitest.config.js',
|
|
113
|
+
'jest.config.js',
|
|
114
|
+
'jest.config.ts',
|
|
115
|
+
'jest.config.mjs',
|
|
116
|
+
'pytest.ini',
|
|
117
|
+
'tox.ini',
|
|
118
|
+
'.eslintrc',
|
|
119
|
+
'.eslintrc.json',
|
|
120
|
+
'.eslintrc.js',
|
|
121
|
+
'.eslintrc.cjs',
|
|
122
|
+
'eslint.config.js',
|
|
123
|
+
'eslint.config.mjs',
|
|
124
|
+
'eslint.config.cjs',
|
|
125
|
+
'eslint.config.ts',
|
|
126
|
+
'.prettierrc',
|
|
127
|
+
'.prettierrc.json',
|
|
128
|
+
'.prettierrc.js',
|
|
129
|
+
'prettier.config.js',
|
|
130
|
+
'prettier.config.mjs',
|
|
131
|
+
'biome.json',
|
|
132
|
+
'ruff.toml',
|
|
133
|
+
'.flake8',
|
|
134
|
+
'mypy.ini',
|
|
135
|
+
'playwright.config.ts',
|
|
136
|
+
'playwright.config.js',
|
|
137
|
+
'cypress.config.ts',
|
|
138
|
+
'cypress.config.js',
|
|
139
|
+
// C / C++ / Build Systems
|
|
140
|
+
'cmakelists.txt',
|
|
141
|
+
'makefile',
|
|
142
|
+
'gnumakefile',
|
|
143
|
+
'meson.build',
|
|
144
|
+
'rakefile',
|
|
145
|
+
// Cloud, Containers, Deployments & CI/CD
|
|
146
|
+
'dockerfile',
|
|
147
|
+
'containerfile',
|
|
148
|
+
'docker-compose.yml',
|
|
149
|
+
'docker-compose.yaml',
|
|
150
|
+
'compose.yaml',
|
|
151
|
+
'compose.yml',
|
|
152
|
+
'.dockerignore',
|
|
153
|
+
'fly.toml',
|
|
154
|
+
'vercel.json',
|
|
155
|
+
'netlify.toml',
|
|
156
|
+
'wrangler.toml',
|
|
157
|
+
'wrangler.json',
|
|
158
|
+
'firebase.json',
|
|
159
|
+
'firestore.rules',
|
|
160
|
+
'firestore.indexes.json',
|
|
161
|
+
'storage.rules',
|
|
162
|
+
'serverless.yml',
|
|
163
|
+
'procfile',
|
|
164
|
+
'app.yaml',
|
|
165
|
+
'cloudbuild.yaml',
|
|
166
|
+
'jenkinsfile',
|
|
167
|
+
'.gitlab-ci.yml',
|
|
168
|
+
// Environments & System Config
|
|
169
|
+
'.env',
|
|
170
|
+
'.env.local',
|
|
171
|
+
'.env.development',
|
|
172
|
+
'.env.production',
|
|
173
|
+
'.env.staging',
|
|
174
|
+
'.env.example',
|
|
175
|
+
'.env.test',
|
|
176
|
+
'.gitignore',
|
|
177
|
+
'.gitattributes',
|
|
178
|
+
'.editorconfig',
|
|
179
|
+
// Documentation & Project Metadata
|
|
180
|
+
'readme.md',
|
|
181
|
+
'contributing.md',
|
|
182
|
+
'changelog.md',
|
|
183
|
+
'license',
|
|
184
|
+
'license.md',
|
|
185
|
+
'architecture.md',
|
|
186
|
+
'features.md',
|
|
187
|
+
'roadmap.md',
|
|
188
|
+
];
|
|
189
|
+
for (const rf of rootFiles) {
|
|
190
|
+
if (lower.includes(rf)) {
|
|
191
|
+
return rf;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
// ─── Main Evaluator ──────────────────────────────────────────────────
|
|
198
|
+
/**
|
|
199
|
+
* Evaluates whether a user request warrants parallel investigation or a single agent.
|
|
200
|
+
* Respects default primary sub-path auto-focusing while allowing explicit overrides.
|
|
23
201
|
*
|
|
24
|
-
* @param userRequest - The user's prompt
|
|
25
|
-
* @param projectType - Detected project type (e.g., "Node.js / TypeScript
|
|
26
|
-
* @param approximateFileCount -
|
|
27
|
-
* @param chatHistory -
|
|
28
|
-
* @
|
|
202
|
+
* @param userRequest - The user's prompt/request
|
|
203
|
+
* @param projectType - Detected project type string (e.g., "Node.js / TypeScript")
|
|
204
|
+
* @param approximateFileCount - Approximate total file count in the project
|
|
205
|
+
* @param chatHistory - Formatted recent conversation history (optional)
|
|
206
|
+
* @param abortSignal - Optional signal to abort the LLM request
|
|
207
|
+
* @param options - Optional sub-path auto-focus and override configuration
|
|
208
|
+
* @returns Structured complexity result with strategy and domain assignments
|
|
29
209
|
*/
|
|
30
|
-
export async function evaluateInvestigationComplexity(userRequest, projectType, approximateFileCount, chatHistory = '', abortSignal) {
|
|
210
|
+
export async function evaluateInvestigationComplexity(userRequest, projectType, approximateFileCount, chatHistory = '', abortSignal, options) {
|
|
31
211
|
if (abortSignal?.aborted) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
throw
|
|
212
|
+
const error = new Error('The operation was aborted');
|
|
213
|
+
error.name = 'AbortError';
|
|
214
|
+
throw error;
|
|
35
215
|
}
|
|
216
|
+
const configuredSubPath = options?.autoFocusSubPath !== undefined
|
|
217
|
+
? options.autoFocusSubPath
|
|
218
|
+
: workspaceRegistry.getPrimarySubPath();
|
|
219
|
+
const activeSubPath = options?.disableAutoFocus ? null : configuredSubPath;
|
|
220
|
+
const explicitOverride = options?.subPathOverride ?? detectSubPathOverride(userRequest, activeSubPath);
|
|
221
|
+
const isAutoFocused = Boolean(activeSubPath && !explicitOverride);
|
|
36
222
|
try {
|
|
37
|
-
const session = createInvestigationComplexitySession();
|
|
223
|
+
const session = await createInvestigationComplexitySession();
|
|
38
224
|
let prompt = `User Request: "${userRequest}"
|
|
39
225
|
Project Type: ${projectType}
|
|
40
226
|
Approximate File Count: ${approximateFileCount}`;
|
|
41
|
-
if (
|
|
42
|
-
|
|
227
|
+
if (activeSubPath) {
|
|
228
|
+
if (explicitOverride) {
|
|
229
|
+
prompt += `\nConfigured Primary Sub-Path: ${activeSubPath}\nSub-Path Override: Active (${explicitOverride}) - scope investigation to requested override.`;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
prompt += `\nConfigured Primary Sub-Path: ${activeSubPath}\nSub-Path Auto-Focus: Active - prioritize and decompose investigation within "${activeSubPath}" unless user request requires broader exploration.`;
|
|
233
|
+
}
|
|
43
234
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
debugLog(`
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
235
|
+
if (chatHistory.trim()) {
|
|
236
|
+
prompt += `\n\nRecent Conversation:\n${chatHistory.trim()}`;
|
|
237
|
+
}
|
|
238
|
+
debugLog(`[InvestigationComplexity] Evaluating prompt complexity (autoFocus: ${activeSubPath || 'none'}, override: ${explicitOverride || 'none'})`);
|
|
239
|
+
const response = await session.sendMessage(prompt, undefined, abortSignal);
|
|
240
|
+
const rawText = typeof response?.response?.text === 'function'
|
|
241
|
+
? response.response.text()
|
|
242
|
+
: typeof response?.text === 'function'
|
|
243
|
+
? response.text()
|
|
244
|
+
: response?.text || '';
|
|
245
|
+
// Clean JSON response (strip markdown fences if present)
|
|
246
|
+
const cleaned = rawText.replace(/```(?:json)?\s*([\s\S]*?)```/g, '$1').trim();
|
|
247
|
+
const parsed = JSON.parse(cleaned);
|
|
248
|
+
// Determine semantic scope and override from AI response with fast token precedence
|
|
249
|
+
let finalScope = 'SUB_PATH';
|
|
250
|
+
let finalOverride = explicitOverride;
|
|
251
|
+
if (parsed.scope === 'FULL_WORKSPACE') {
|
|
252
|
+
finalScope = 'FULL_WORKSPACE';
|
|
253
|
+
if (!finalOverride)
|
|
254
|
+
finalOverride = parsed.subPathOverride || 'root';
|
|
255
|
+
}
|
|
256
|
+
else if (parsed.scope === 'EXTERNAL_WORKSPACE') {
|
|
257
|
+
finalScope = 'EXTERNAL_WORKSPACE';
|
|
258
|
+
if (!finalOverride)
|
|
259
|
+
finalOverride = parsed.subPathOverride || '@alias';
|
|
260
|
+
}
|
|
261
|
+
else if (parsed.scope === 'SUB_PATH') {
|
|
262
|
+
finalScope = 'SUB_PATH';
|
|
263
|
+
if (!options?.subPathOverride)
|
|
264
|
+
finalOverride = null;
|
|
265
|
+
}
|
|
266
|
+
else if (parsed.subPathOverride && !finalOverride) {
|
|
267
|
+
finalOverride = parsed.subPathOverride;
|
|
268
|
+
finalScope = (finalOverride && finalOverride.startsWith('@')) ? 'EXTERNAL_WORKSPACE' : 'FULL_WORKSPACE';
|
|
269
|
+
}
|
|
270
|
+
const finalIsAutoFocused = Boolean(activeSubPath && !finalOverride);
|
|
271
|
+
const rawStrategy = typeof parsed.strategy === 'string' ? parsed.strategy.toUpperCase() : '';
|
|
272
|
+
const rawDomains = Array.isArray(parsed.domains)
|
|
273
|
+
? parsed.domains.map(String).filter(Boolean)
|
|
274
|
+
: [];
|
|
275
|
+
const rawAssignments = Array.isArray(parsed.agentAssignments) ? parsed.agentAssignments : [];
|
|
276
|
+
// Permissive strategy determination: trigger PARALLEL if explicitly requested OR if multiple domains/assignments exist
|
|
277
|
+
const isParallel = rawStrategy === 'PARALLEL' || rawAssignments.length >= 2 || rawDomains.length >= 2;
|
|
278
|
+
// If SINGLE without multi-domain signals, return SINGLE
|
|
279
|
+
if (!isParallel) {
|
|
280
|
+
debugLog(`[InvestigationComplexity] Strategy: SINGLE (Scope: ${finalScope}, Reason: ${parsed.reasoning || 'N/A'})`);
|
|
51
281
|
return {
|
|
52
282
|
strategy: 'SINGLE',
|
|
283
|
+
scope: finalScope,
|
|
53
284
|
domains: [],
|
|
54
285
|
agentAssignments: [],
|
|
55
|
-
reasoning: parsed.reasoning || '
|
|
286
|
+
reasoning: parsed.reasoning || 'Localized prompt — single agent sufficient.',
|
|
287
|
+
focusedSubPath: activeSubPath,
|
|
288
|
+
subPathOverride: finalOverride,
|
|
289
|
+
isAutoFocused: finalIsAutoFocused,
|
|
56
290
|
};
|
|
57
291
|
}
|
|
58
|
-
// Validate
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
}
|
|
292
|
+
// Validate agent assignments for PARALLEL
|
|
293
|
+
let agentAssignments = [];
|
|
294
|
+
for (const a of rawAssignments) {
|
|
295
|
+
if (typeof a?.agentLabel === 'string' && Array.isArray(a?.domains) && a.domains.length > 0) {
|
|
296
|
+
agentAssignments.push({
|
|
297
|
+
agentLabel: a.agentLabel.trim(),
|
|
298
|
+
domains: a.domains.map(String).filter(Boolean),
|
|
299
|
+
});
|
|
68
300
|
}
|
|
69
301
|
}
|
|
70
|
-
//
|
|
302
|
+
// Permissive Auto-Recovery: if agentAssignments was missing/incomplete but multiple domains were identified
|
|
303
|
+
if (agentAssignments.length < 2 && rawDomains.length >= 2) {
|
|
304
|
+
debugLog('[InvestigationComplexity] Auto-constructing agent assignments from identified domains');
|
|
305
|
+
if (rawDomains.length === 2) {
|
|
306
|
+
agentAssignments = rawDomains.map((d) => ({
|
|
307
|
+
agentLabel: `${d} Specialist`,
|
|
308
|
+
domains: [d],
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const mid = Math.ceil(rawDomains.length / 2);
|
|
313
|
+
agentAssignments = [
|
|
314
|
+
{
|
|
315
|
+
agentLabel: `${rawDomains[0]} & Subsystems`,
|
|
316
|
+
domains: rawDomains.slice(0, mid),
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
agentLabel: `${rawDomains[mid]} & Related`,
|
|
320
|
+
domains: rawDomains.slice(mid),
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// If still fewer than 2 valid assignments after auto-recovery, fall back to SINGLE
|
|
71
326
|
if (agentAssignments.length < 2) {
|
|
72
|
-
debugLog('
|
|
327
|
+
debugLog('[InvestigationComplexity] PARALLEL requested but <2 valid assignments — falling back to SINGLE');
|
|
73
328
|
return {
|
|
74
329
|
strategy: 'SINGLE',
|
|
330
|
+
scope: finalScope,
|
|
75
331
|
domains: [],
|
|
76
332
|
agentAssignments: [],
|
|
77
|
-
reasoning:
|
|
333
|
+
reasoning: parsed.reasoning || 'Insufficient domain separation for parallel investigation.',
|
|
334
|
+
focusedSubPath: activeSubPath,
|
|
335
|
+
subPathOverride: finalOverride,
|
|
336
|
+
isAutoFocused: finalIsAutoFocused,
|
|
78
337
|
};
|
|
79
338
|
}
|
|
339
|
+
let allDomains = rawDomains;
|
|
340
|
+
if (allDomains.length === 0 && agentAssignments.length > 0) {
|
|
341
|
+
allDomains = Array.from(new Set(agentAssignments.flatMap((a) => a.domains)));
|
|
342
|
+
}
|
|
343
|
+
debugLog(`[InvestigationComplexity] Strategy: PARALLEL with ${agentAssignments.length} agents: ${agentAssignments.map(a => a.agentLabel).join(', ')} (Scope: ${finalScope})`);
|
|
80
344
|
return {
|
|
81
345
|
strategy: 'PARALLEL',
|
|
82
|
-
|
|
346
|
+
scope: finalScope,
|
|
347
|
+
domains: allDomains,
|
|
83
348
|
agentAssignments,
|
|
84
|
-
reasoning: parsed.reasoning || '',
|
|
349
|
+
reasoning: parsed.reasoning || 'Multi-domain parallel investigation dispatched.',
|
|
350
|
+
focusedSubPath: activeSubPath,
|
|
351
|
+
subPathOverride: finalOverride,
|
|
352
|
+
isAutoFocused: finalIsAutoFocused,
|
|
85
353
|
};
|
|
86
354
|
}
|
|
87
|
-
catch (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
err
|
|
355
|
+
catch (err) {
|
|
356
|
+
// If aborted, rethrow
|
|
357
|
+
if (abortSignal?.aborted ||
|
|
358
|
+
err?.name === 'AbortError' ||
|
|
359
|
+
err?.name === 'CanceledError' ||
|
|
360
|
+
err?.code === 'ABORT_ERR' ||
|
|
361
|
+
err?.message?.includes('aborted') ||
|
|
362
|
+
err?.message?.includes('abort') ||
|
|
363
|
+
err?.message?.includes('canceled')) {
|
|
364
|
+
if (err && !err.name)
|
|
365
|
+
err.name = 'AbortError';
|
|
91
366
|
throw err;
|
|
92
367
|
}
|
|
93
|
-
debugLog(`
|
|
368
|
+
debugLog(`[InvestigationComplexity] Evaluation failed (${err?.message}) — falling back to SINGLE`);
|
|
94
369
|
return {
|
|
95
370
|
strategy: 'SINGLE',
|
|
371
|
+
scope: 'SUB_PATH',
|
|
96
372
|
domains: [],
|
|
97
373
|
agentAssignments: [],
|
|
98
374
|
reasoning: 'Evaluator failed — falling back to single agent.',
|
|
375
|
+
focusedSubPath: activeSubPath,
|
|
376
|
+
subPathOverride: explicitOverride,
|
|
377
|
+
isAutoFocused,
|
|
99
378
|
};
|
|
100
379
|
}
|
|
101
380
|
}
|
|
@@ -52,7 +52,7 @@ export class InvestigationAgentRunner {
|
|
|
52
52
|
this.projectType = projectType;
|
|
53
53
|
let model = getGlobalActiveModel();
|
|
54
54
|
if (model === GEMINI_MODELS.AUTO || model.includes('claude'))
|
|
55
|
-
model = GEMINI_MODELS.
|
|
55
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
56
56
|
this.chat = new ProxyChatSession(model, this.buildSystemInstruction(), getContextToolDeclarations(), {
|
|
57
57
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
58
58
|
temperature: 1,
|
|
@@ -66,6 +66,11 @@ export class InvestigationOrchestrator {
|
|
|
66
66
|
for (let i = 0; i < agents.length; i += MAX_CONCURRENT) {
|
|
67
67
|
if (abortSignal.aborted)
|
|
68
68
|
break;
|
|
69
|
+
if (i > 0) {
|
|
70
|
+
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.PARALLEL_CHUNK_MS));
|
|
71
|
+
if (abortSignal.aborted)
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
69
74
|
const chunk = agents.slice(i, i + MAX_CONCURRENT);
|
|
70
75
|
const chunkAssignments = agentAssignments.slice(i, i + MAX_CONCURRENT);
|
|
71
76
|
const chunkPromises = chunk.map((agent, chunkIndex) => {
|
|
@@ -12,7 +12,7 @@ import * as p from '@clack/prompts';
|
|
|
12
12
|
import pc from 'picocolors';
|
|
13
13
|
import { ProxyChatSession, getGlobalActiveModel, compressTextUsingFlashLite } from '../ai.js';
|
|
14
14
|
import { peekTurnUsage } from '../proxyClient.js';
|
|
15
|
-
import { GEMINI_MODELS, MAX_OUTPUT_TOKENS } from '../../utils/config.js';
|
|
15
|
+
import { GEMINI_MODELS, MAX_OUTPUT_TOKENS, TPM_COOLING_DELAYS } from '../../utils/config.js';
|
|
16
16
|
import { debugLog } from '../../utils/logger.js';
|
|
17
17
|
import { MessageBus } from './messageBus.js';
|
|
18
18
|
import { FileLockRegistry } from './fileLockRegistry.js';
|
|
@@ -100,6 +100,10 @@ export class Orchestrator {
|
|
|
100
100
|
p.log.info(pc.blue(`Orchestrator: Generated ${waves.length} execution wave(s) with ${graph.tasks.length} total tasks.`));
|
|
101
101
|
// 3. Dispatch Waves
|
|
102
102
|
for (const wave of waves) {
|
|
103
|
+
if (signal.aborted)
|
|
104
|
+
break;
|
|
105
|
+
// Cooling-off pause before dispatching each execution wave
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.ORCHESTRATION_WAVE_MS));
|
|
103
107
|
if (signal.aborted)
|
|
104
108
|
break;
|
|
105
109
|
const taskDescriptions = wave.taskIds
|
|
@@ -126,6 +130,11 @@ export class Orchestrator {
|
|
|
126
130
|
for (let i = 0; i < wave.taskIds.length; i += MAX_CONCURRENT) {
|
|
127
131
|
if (signal.aborted)
|
|
128
132
|
break;
|
|
133
|
+
if (i > 0) {
|
|
134
|
+
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.PARALLEL_CHUNK_MS));
|
|
135
|
+
if (signal.aborted)
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
129
138
|
const chunk = wave.taskIds.slice(i, i + MAX_CONCURRENT);
|
|
130
139
|
const wavePromises = chunk.map((taskId) => {
|
|
131
140
|
const taskDef = graph.tasks.find((t) => t.id === taskId);
|
|
@@ -169,7 +178,10 @@ export class Orchestrator {
|
|
|
169
178
|
}
|
|
170
179
|
}
|
|
171
180
|
// 4. Reconciliation
|
|
172
|
-
|
|
181
|
+
if (!signal.aborted) {
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.ORCHESTRATION_RECONCILE_MS));
|
|
183
|
+
}
|
|
184
|
+
const finalSummary = await this.reconcile(graph, signal);
|
|
173
185
|
return finalSummary;
|
|
174
186
|
}
|
|
175
187
|
/**
|
|
@@ -248,7 +260,7 @@ export class Orchestrator {
|
|
|
248
260
|
/**
|
|
249
261
|
* Final reconciliation phase after all waves complete.
|
|
250
262
|
*/
|
|
251
|
-
async reconcile(graph) {
|
|
263
|
+
async reconcile(graph, signal) {
|
|
252
264
|
p.log.step(pc.cyan('Orchestrator: Reconciling results'));
|
|
253
265
|
const stats = this.bus.getStats();
|
|
254
266
|
let totalTokens = 0;
|
|
@@ -280,7 +292,7 @@ export class Orchestrator {
|
|
|
280
292
|
'DO NOT list changes by task name or separate them by agent. ' +
|
|
281
293
|
'Be concise, helpful, and conclude by asking if they need any further adjustments.\n' +
|
|
282
294
|
'</directives>';
|
|
283
|
-
const synthesized = await compressTextUsingFlashLite(payloadWithContext, instruction, undefined, true);
|
|
295
|
+
const synthesized = await compressTextUsingFlashLite(payloadWithContext, instruction, undefined, true, signal);
|
|
284
296
|
finalSummary += synthesized + '\n\n';
|
|
285
297
|
}
|
|
286
298
|
catch (e) {
|
|
@@ -1,57 +1,34 @@
|
|
|
1
1
|
import { MessageBus } from './messageBus.js';
|
|
2
2
|
import { FileLockRegistry } from './fileLockRegistry.js';
|
|
3
|
+
import { type PathResolutionOptions } from '../../utils/pathSecurity.js';
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* Resolves a file path to its canonical absolute path for lock registry keying,
|
|
6
|
+
* accounting for multi-workspace alias resolution and default primary sub-path auto-focusing.
|
|
7
|
+
*
|
|
8
|
+
* @param workspaceRoot - The base primary workspace root directory
|
|
9
|
+
* @param filePath - The relative or aliased file path
|
|
10
|
+
* @param options - Optional sub-path override configuration
|
|
11
|
+
* @returns Absolute canonical file path
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveCanonicalLockPath(workspaceRoot: string, filePath: string, options?: PathResolutionOptions): string;
|
|
14
|
+
/**
|
|
15
|
+
* Returns Gemini tool declarations available to sub-agents during orchestration.
|
|
16
|
+
* In addition to standard agent tools (read/write/search/command/etc.), includes
|
|
17
|
+
* inter-agent communication tools (post_message, read_messages).
|
|
6
18
|
*/
|
|
7
|
-
export declare function getScopedToolDeclarations():
|
|
8
|
-
name: string;
|
|
9
|
-
description: string;
|
|
10
|
-
parameters: {
|
|
11
|
-
type: string;
|
|
12
|
-
properties: {
|
|
13
|
-
type: {
|
|
14
|
-
type: string;
|
|
15
|
-
description: string;
|
|
16
|
-
};
|
|
17
|
-
content: {
|
|
18
|
-
type: string;
|
|
19
|
-
description: string;
|
|
20
|
-
};
|
|
21
|
-
toAgent: {
|
|
22
|
-
type: string;
|
|
23
|
-
description: string;
|
|
24
|
-
};
|
|
25
|
-
affectedFiles: {
|
|
26
|
-
type: string;
|
|
27
|
-
items: {
|
|
28
|
-
type: string;
|
|
29
|
-
};
|
|
30
|
-
description: string;
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
required: string[];
|
|
34
|
-
};
|
|
35
|
-
} | {
|
|
36
|
-
name: string;
|
|
37
|
-
description: string;
|
|
38
|
-
parameters: {
|
|
39
|
-
type: string;
|
|
40
|
-
properties: {
|
|
41
|
-
type?: undefined;
|
|
42
|
-
content?: undefined;
|
|
43
|
-
toAgent?: undefined;
|
|
44
|
-
affectedFiles?: undefined;
|
|
45
|
-
};
|
|
46
|
-
required?: undefined;
|
|
47
|
-
};
|
|
48
|
-
})[];
|
|
19
|
+
export declare function getScopedToolDeclarations(): any[];
|
|
49
20
|
/**
|
|
50
|
-
*
|
|
21
|
+
* Executes a tool within the sub-agent execution boundary.
|
|
22
|
+
* Handles concurrency locking for file-mutating operations, sends progress
|
|
23
|
+
* notifications to the orchestrator, and logs activity to the message bus.
|
|
51
24
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
25
|
+
* @param name - Tool function name
|
|
26
|
+
* @param args - Tool arguments object
|
|
27
|
+
* @param workspaceRoot - Primary workspace root directory
|
|
28
|
+
* @param agentId - Unique ID of the executing agent
|
|
29
|
+
* @param bus - Shared message bus instance
|
|
30
|
+
* @param locks - Shared file lock registry instance
|
|
31
|
+
* @param onProgress - Callback to notify parent of sub-agent progress
|
|
32
|
+
* @param options - Optional sub-path auto-focus and override configuration
|
|
56
33
|
*/
|
|
57
|
-
export declare function executeScopedTool(name: string, args: Record<string, any>, workspaceRoot: string, agentId: string, bus: MessageBus, locks: FileLockRegistry, onProgress: (msg?: string) => void): Promise<any>;
|
|
34
|
+
export declare function executeScopedTool(name: string, args: Record<string, any>, workspaceRoot: string, agentId: string, bus: MessageBus, locks: FileLockRegistry, onProgress: (msg?: string) => void, options?: PathResolutionOptions): Promise<any>;
|