minovative-mind-cli 2.11.4 → 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/ai.d.ts +1 -1
- package/dist/services/ai.js +40 -7
- package/dist/services/investigationComplexity.d.ts +39 -13
- package/dist/services/investigationComplexity.js +325 -46
- package/dist/services/orchestration/scopedTools.d.ts +27 -50
- package/dist/services/orchestration/scopedTools.js +60 -18
- 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/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 +38 -23
- 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
|
}
|
|
@@ -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>;
|
|
@@ -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;
|