minovative-mind-cli 2.11.5 → 2.13.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.
- package/README.md +27 -1
- package/dist/commands/chat.js +3 -1
- package/dist/commands/eval.d.ts +22 -0
- package/dist/commands/eval.js +141 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/agent/slashCommands.js +4 -2
- package/dist/services/agent/toolLoop.d.ts +4 -0
- package/dist/services/agent/toolLoop.js +61 -10
- package/dist/services/agent-tools.d.ts +5 -5
- package/dist/services/agent-tools.js +150 -11
- package/dist/services/contextAgent.d.ts +1 -0
- package/dist/services/contextAgent.js +29 -6
- package/dist/services/ideOptimization.d.ts +15 -0
- package/dist/services/ideOptimization.js +169 -0
- package/dist/services/metrics.d.ts +10 -0
- package/dist/services/metrics.js +24 -0
- package/dist/services/orchestration/messageBus.d.ts +81 -41
- package/dist/services/orchestration/messageBus.js +242 -98
- package/dist/services/orchestration/orchestrator.d.ts +6 -6
- package/dist/services/orchestration/orchestrator.js +32 -21
- package/dist/services/orchestration/scopedTools.d.ts +7 -1
- package/dist/services/orchestration/scopedTools.js +45 -9
- package/dist/services/orchestration/subAgent.d.ts +19 -17
- package/dist/services/orchestration/subAgent.js +98 -81
- package/dist/services/swebench/gitDiffExtractor.d.ts +57 -0
- package/dist/services/swebench/gitDiffExtractor.js +209 -0
- package/dist/services/swebench/index.d.ts +4 -0
- package/dist/services/swebench/index.js +4 -0
- package/dist/services/swebench/instanceLoader.d.ts +21 -0
- package/dist/services/swebench/instanceLoader.js +171 -0
- package/dist/services/swebench/sweBenchRunnerService.d.ts +38 -0
- package/dist/services/swebench/sweBenchRunnerService.js +618 -0
- package/dist/services/swebench/types.d.ts +167 -0
- package/dist/services/swebench/types.js +7 -0
- package/dist/services/verificationService.js +3 -0
- package/dist/utils/fuzzyMatch.d.ts +51 -21
- package/dist/utils/fuzzyMatch.js +37 -122
- package/dist/utils/projectStorage.js +10 -5
- package/dist/utils/systemPrompts.d.ts +1 -1
- package/dist/utils/systemPrompts.js +10 -4
- package/oclif.manifest.json +137 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @file Two-Layer Message Bus for Sub-Agent Orchestration.
|
|
3
3
|
*
|
|
4
4
|
* Provides the core inter-agent communication primitive for the orchestration system.
|
|
5
5
|
* Two layers of communication:
|
|
@@ -19,6 +19,38 @@ import { promises as fs, readFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
|
19
19
|
import path from 'node:path';
|
|
20
20
|
import { atomicWriteFile } from '../../utils/atomicWrite.js';
|
|
21
21
|
import { debugLog } from '../../utils/logger.js';
|
|
22
|
+
export const MUTATING_TOOLS = new Set([
|
|
23
|
+
'write_file',
|
|
24
|
+
'modify_file',
|
|
25
|
+
'delete_file',
|
|
26
|
+
'rename_file',
|
|
27
|
+
'run_command',
|
|
28
|
+
]);
|
|
29
|
+
export function coalesceActivityEntries(entries) {
|
|
30
|
+
const result = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const last = result[result.length - 1];
|
|
33
|
+
if (last && last.agentId === entry.agentId && last.tool === entry.tool && last.target === entry.target) {
|
|
34
|
+
last.count++;
|
|
35
|
+
last.status = entry.status; // most recent status
|
|
36
|
+
if (entry.resultSummary) {
|
|
37
|
+
last.resultSummary = entry.resultSummary;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
result.push({
|
|
42
|
+
agentId: entry.agentId,
|
|
43
|
+
tool: entry.tool,
|
|
44
|
+
target: entry.target,
|
|
45
|
+
action: entry.action,
|
|
46
|
+
count: 1,
|
|
47
|
+
status: entry.status,
|
|
48
|
+
resultSummary: entry.resultSummary,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
22
54
|
// ─── Message Bus Implementation ──────────────────────────────────────
|
|
23
55
|
/**
|
|
24
56
|
* Two-layer, disk-backed message bus for sub-agent coordination.
|
|
@@ -31,15 +63,15 @@ import { debugLog } from '../../utils/logger.js';
|
|
|
31
63
|
* a CLI crash and resume from the last known state.
|
|
32
64
|
*/
|
|
33
65
|
export class MessageBus {
|
|
34
|
-
|
|
35
|
-
|
|
66
|
+
/** Maximum semantic signals any single agent can post */
|
|
67
|
+
static MAX_SIGNALS_PER_AGENT = 50;
|
|
36
68
|
activityCursors = new Map();
|
|
37
|
-
|
|
38
|
-
persistQueue = Promise.resolve();
|
|
69
|
+
activityLog = [];
|
|
39
70
|
persistPath;
|
|
71
|
+
persistQueue = Promise.resolve();
|
|
72
|
+
signalCursors = new Map();
|
|
73
|
+
signals = [];
|
|
40
74
|
workspaceRoot;
|
|
41
|
-
/** Maximum semantic signals any single agent can post */
|
|
42
|
-
static MAX_SIGNALS_PER_AGENT = 50;
|
|
43
75
|
constructor(workspaceRoot, conversationId) {
|
|
44
76
|
this.workspaceRoot = workspaceRoot;
|
|
45
77
|
const orchestrationDir = path.join(workspaceRoot, '.minovativemind', 'orchestration');
|
|
@@ -48,50 +80,70 @@ export class MessageBus {
|
|
|
48
80
|
}
|
|
49
81
|
// ─── Layer 1: Automatic Activity Log ─────────────────────────────
|
|
50
82
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
83
|
+
* Formats a batch of activity entries into a compact, human-readable string
|
|
84
|
+
* without token-wasteful column whitespace padding. Coalesces repeated actions.
|
|
53
85
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
86
|
+
static formatActivityEntries(entries) {
|
|
87
|
+
if (entries.length === 0)
|
|
88
|
+
return '';
|
|
89
|
+
const coalesced = coalesceActivityEntries(entries);
|
|
90
|
+
return coalesced
|
|
91
|
+
.map((e) => {
|
|
92
|
+
const countSuffix = e.count > 1 ? ` (${e.count} ops)` : '';
|
|
93
|
+
const statusPrefix = e.status === 'error' ? '[FAILED] ' : '';
|
|
94
|
+
const summary = e.resultSummary ? ` | ${e.resultSummary}` : '';
|
|
95
|
+
return ` [${e.agentId}] ${statusPrefix}${e.tool} ${e.target}: ${e.action}${countSuffix}${summary}`;
|
|
96
|
+
})
|
|
97
|
+
.join('\n');
|
|
57
98
|
}
|
|
58
99
|
// ─── Layer 2: Semantic Signals ───────────────────────────────────
|
|
59
100
|
/**
|
|
60
|
-
*
|
|
61
|
-
* `post_message`) but carry intent that raw tool logs cannot express.
|
|
62
|
-
*
|
|
63
|
-
* Enforces per-agent signal cap to prevent runaway agents from flooding the bus.
|
|
64
|
-
*
|
|
65
|
-
* @returns `true` if the signal was accepted, `false` if the agent hit the cap.
|
|
101
|
+
* Formats semantic signals into a readable string for agent context injection.
|
|
66
102
|
*/
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
103
|
+
static formatSignals(signals) {
|
|
104
|
+
if (signals.length === 0)
|
|
105
|
+
return '';
|
|
106
|
+
return signals
|
|
107
|
+
.map((s) => {
|
|
108
|
+
const tag = s.type.toUpperCase();
|
|
109
|
+
switch (s.type) {
|
|
110
|
+
case 'discovery':
|
|
111
|
+
return ` [${tag} from ${s.fromAgent}]: "${s.content}" (affects: ${s.affectedFiles.join(', ')})`;
|
|
112
|
+
case 'warning':
|
|
113
|
+
return ` [${tag} from ${s.fromAgent}]: "${s.content}"${s.changedSignature ? ` (signature: ${s.changedSignature})` : ''}`;
|
|
114
|
+
case 'request':
|
|
115
|
+
return ` [${tag} from ${s.fromAgent} → ${s.toAgent}]: "${s.content}"`;
|
|
116
|
+
case 'completion':
|
|
117
|
+
return ` [${tag} from ${s.fromAgent}]: "${s.summary}"`;
|
|
118
|
+
default:
|
|
119
|
+
return ` [SIGNAL from ${s.fromAgent}]: ${JSON.stringify(s)}`;
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
.join('\n');
|
|
77
123
|
}
|
|
78
124
|
// ─── Reading ─────────────────────────────────────────────────────
|
|
79
125
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* Filters out the requesting agent's own entries (an agent doesn't need to
|
|
84
|
-
* re-read its own tool logs or signals).
|
|
126
|
+
* Clears all bus state and removes the persistence file.
|
|
127
|
+
* Called when orchestration completes successfully (no crash recovery needed).
|
|
85
128
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
129
|
+
async cleanup() {
|
|
130
|
+
this.activityLog = [];
|
|
131
|
+
this.signals = [];
|
|
132
|
+
this.activityCursors = new Map();
|
|
133
|
+
this.signalCursors = new Map();
|
|
134
|
+
try {
|
|
135
|
+
await fs.unlink(this.persistPath);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// File may not exist — non-fatal
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns all activity entries for a specific agent (used for dead agent
|
|
143
|
+
* recovery — collecting partial progress before re-dispatch).
|
|
144
|
+
*/
|
|
145
|
+
getAgentActivity(agentId) {
|
|
146
|
+
return this.activityLog.filter((e) => e.agentId === agentId);
|
|
95
147
|
}
|
|
96
148
|
/**
|
|
97
149
|
* Returns the complete, unfiltered bus state for PM reconciliation.
|
|
@@ -103,13 +155,6 @@ export class MessageBus {
|
|
|
103
155
|
signals: [...this.signals],
|
|
104
156
|
};
|
|
105
157
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Returns all activity entries for a specific agent (used for dead agent
|
|
108
|
-
* recovery — collecting partial progress before re-dispatch).
|
|
109
|
-
*/
|
|
110
|
-
getAgentActivity(agentId) {
|
|
111
|
-
return this.activityLog.filter((e) => e.agentId === agentId);
|
|
112
|
-
}
|
|
113
158
|
/**
|
|
114
159
|
* Returns the total number of activity entries and signals in the bus.
|
|
115
160
|
* Used for terminal display and diagnostics.
|
|
@@ -120,47 +165,162 @@ export class MessageBus {
|
|
|
120
165
|
signalCount: this.signals.length,
|
|
121
166
|
};
|
|
122
167
|
}
|
|
123
|
-
// ─── Formatting Helpers ──────────────────────────────────────────
|
|
124
168
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
169
|
+
* Retrieves all unread activity entries and semantic signals for a specific agent.
|
|
170
|
+
* Defaults to state-mutating events and unicast signal routing to eliminate token noise.
|
|
127
171
|
*/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const summary = e.resultSummary ? ` | ${e.resultSummary}` : '';
|
|
135
|
-
return ` ${e.agentId} | ${e.tool.padEnd(14)} → ${e.target.padEnd(40)} | ${statusIcon} ${e.action}${summary}`;
|
|
136
|
-
})
|
|
137
|
-
.join('\n');
|
|
172
|
+
getUnread(agentId, options) {
|
|
173
|
+
return this.queryBus({
|
|
174
|
+
agentId,
|
|
175
|
+
onlyMutations: options?.onlyMutations ?? true,
|
|
176
|
+
advanceCursor: options?.advanceCursor ?? true,
|
|
177
|
+
});
|
|
138
178
|
}
|
|
139
179
|
/**
|
|
140
|
-
*
|
|
180
|
+
* Records a tool execution into the activity log. Called by the scoped tool
|
|
181
|
+
* wrapper in `scopedTools.ts` — zero cost to the agent.
|
|
141
182
|
*/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
183
|
+
logActivity(entry) {
|
|
184
|
+
this.activityLog.push(entry);
|
|
185
|
+
this.persistToDiskAsync();
|
|
186
|
+
}
|
|
187
|
+
// ─── Formatting Helpers ──────────────────────────────────────────
|
|
188
|
+
/**
|
|
189
|
+
* Peeks urgent signals (breaking warnings or direct requests) that are relevant
|
|
190
|
+
* to a sub-agent's task scope without advancing its cursor. Used for in-band notice
|
|
191
|
+
* delivery in scopedTools.
|
|
192
|
+
*/
|
|
193
|
+
peekUrgentSignals(agentId, scope) {
|
|
194
|
+
const sigCursor = this.signalCursors.get(agentId) ?? 0;
|
|
195
|
+
const unreadSignals = this.signals.slice(sigCursor).filter((s) => s.fromAgent !== agentId);
|
|
196
|
+
const urgent = [];
|
|
197
|
+
for (const signal of unreadSignals) {
|
|
198
|
+
// 1. Direct targeted request to this agent
|
|
199
|
+
if (signal.type === 'request' && (signal.toAgent === agentId || signal.toAgent === 'all')) {
|
|
200
|
+
urgent.push(signal);
|
|
201
|
+
continue;
|
|
159
202
|
}
|
|
160
|
-
|
|
161
|
-
.
|
|
203
|
+
// 2. Warnings from upstream dependencies or affecting target files
|
|
204
|
+
if (signal.type === 'warning') {
|
|
205
|
+
if (!scope || !scope.dependsOn || scope.dependsOn.includes(signal.fromAgent)) {
|
|
206
|
+
urgent.push(signal);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// 3. Upstream completion signals from direct dependencies
|
|
211
|
+
if (signal.type === 'completion' && scope?.dependsOn && scope.dependsOn.includes(signal.fromAgent)) {
|
|
212
|
+
urgent.push(signal);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return urgent;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Posts a semantic signal from an agent. These cost tokens (the agent calls
|
|
220
|
+
* `post_message`) but carry intent that raw tool logs cannot express.
|
|
221
|
+
*
|
|
222
|
+
* Enforces per-agent signal cap to prevent runaway agents from flooding the bus.
|
|
223
|
+
*
|
|
224
|
+
* @returns `true` if the signal was accepted, `false` if the agent hit the cap.
|
|
225
|
+
*/
|
|
226
|
+
postSignal(signal) {
|
|
227
|
+
const agentSignalCount = this.signals.filter((s) => s.fromAgent === signal.fromAgent).length;
|
|
228
|
+
if (agentSignalCount >= MessageBus.MAX_SIGNALS_PER_AGENT) {
|
|
229
|
+
debugLog(`MessageBus: Agent ${signal.fromAgent} hit signal cap (${MessageBus.MAX_SIGNALS_PER_AGENT}). ` +
|
|
230
|
+
`Dropping signal of type "${signal.type}".`);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
this.signals.push(signal);
|
|
234
|
+
this.persistToDiskAsync();
|
|
235
|
+
return true;
|
|
162
236
|
}
|
|
163
237
|
// ─── Persistence ─────────────────────────────────────────────────
|
|
238
|
+
/**
|
|
239
|
+
* Performs an intelligent, scoped query against unread bus activity and signals.
|
|
240
|
+
* Filters out read-only noise for peer sub-agents, enforces unicast delivery for targeted
|
|
241
|
+
* requests, and supports selective querying by file, agent, or signal type.
|
|
242
|
+
*/
|
|
243
|
+
queryBus(options) {
|
|
244
|
+
const actCursor = this.activityCursors.get(options.agentId) ?? 0;
|
|
245
|
+
const sigCursor = this.signalCursors.get(options.agentId) ?? 0;
|
|
246
|
+
const onlyMutations = options.onlyMutations ?? true;
|
|
247
|
+
const normalizedFile = options.file ? options.file.replace(/\\/g, '/').toLowerCase() : undefined;
|
|
248
|
+
const activities = this.activityLog.slice(actCursor).filter((e) => {
|
|
249
|
+
// Exclude own actions
|
|
250
|
+
if (e.agentId === options.agentId)
|
|
251
|
+
return false;
|
|
252
|
+
// Filter read-only tools if onlyMutations is enabled
|
|
253
|
+
if (onlyMutations && !MUTATING_TOOLS.has(e.tool))
|
|
254
|
+
return false;
|
|
255
|
+
// Optional file filter
|
|
256
|
+
if (normalizedFile) {
|
|
257
|
+
const entryTarget = e.target.replace(/\\/g, '/').toLowerCase();
|
|
258
|
+
if (!entryTarget.includes(normalizedFile) && !normalizedFile.includes(entryTarget)) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Optional fromAgent filter
|
|
263
|
+
if (options.fromAgent && e.agentId !== options.fromAgent)
|
|
264
|
+
return false;
|
|
265
|
+
return true;
|
|
266
|
+
});
|
|
267
|
+
const signals = this.signals.slice(sigCursor).filter((s) => {
|
|
268
|
+
// Exclude own signals
|
|
269
|
+
if (s.fromAgent === options.agentId)
|
|
270
|
+
return false;
|
|
271
|
+
// Unicast request routing: only deliver if targeted to this agent or broadcast to 'all'
|
|
272
|
+
if (s.type === 'request') {
|
|
273
|
+
const req = s;
|
|
274
|
+
if (req.toAgent && req.toAgent !== 'all' && req.toAgent !== options.agentId) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Optional type filter
|
|
279
|
+
if (options.type && s.type !== options.type)
|
|
280
|
+
return false;
|
|
281
|
+
// Optional fromAgent filter
|
|
282
|
+
if (options.fromAgent && s.fromAgent !== options.fromAgent)
|
|
283
|
+
return false;
|
|
284
|
+
// Optional file filter for signals
|
|
285
|
+
if (normalizedFile) {
|
|
286
|
+
if (s.type === 'discovery') {
|
|
287
|
+
const disc = s;
|
|
288
|
+
const touches = (disc.affectedFiles || []).some((f) => {
|
|
289
|
+
const norm = f.replace(/\\/g, '/').toLowerCase();
|
|
290
|
+
return norm.includes(normalizedFile) || normalizedFile.includes(norm);
|
|
291
|
+
});
|
|
292
|
+
if (!touches && !disc.content.toLowerCase().includes(normalizedFile))
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
else if (s.type === 'warning') {
|
|
296
|
+
const warn = s;
|
|
297
|
+
const touches = warn.content.toLowerCase().includes(normalizedFile) ||
|
|
298
|
+
Boolean(warn.changedSignature && warn.changedSignature.toLowerCase().includes(normalizedFile));
|
|
299
|
+
if (!touches)
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
else if (s.type === 'request') {
|
|
303
|
+
const req = s;
|
|
304
|
+
if (!req.content.toLowerCase().includes(normalizedFile))
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
else if (s.type === 'completion') {
|
|
308
|
+
const comp = s;
|
|
309
|
+
const touches = comp.summary.toLowerCase().includes(normalizedFile) ||
|
|
310
|
+
Object.keys(comp.exports || {}).some((k) => k.toLowerCase().includes(normalizedFile));
|
|
311
|
+
if (!touches)
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return true;
|
|
316
|
+
});
|
|
317
|
+
// Advance cursors if requested (default: true)
|
|
318
|
+
if (options.advanceCursor !== false) {
|
|
319
|
+
this.activityCursors.set(options.agentId, this.activityLog.length);
|
|
320
|
+
this.signalCursors.set(options.agentId, this.signals.length);
|
|
321
|
+
}
|
|
322
|
+
return { activities, signals };
|
|
323
|
+
}
|
|
164
324
|
/**
|
|
165
325
|
* Writes the full bus state to disk atomically. Called after every mutation
|
|
166
326
|
* to ensure crash resilience. Uses fire-and-forget to avoid blocking the
|
|
@@ -209,20 +369,4 @@ export class MessageBus {
|
|
|
209
369
|
this.signalCursors = new Map();
|
|
210
370
|
}
|
|
211
371
|
}
|
|
212
|
-
/**
|
|
213
|
-
* Clears all bus state and removes the persistence file.
|
|
214
|
-
* Called when orchestration completes successfully (no crash recovery needed).
|
|
215
|
-
*/
|
|
216
|
-
async cleanup() {
|
|
217
|
-
this.activityLog = [];
|
|
218
|
-
this.signals = [];
|
|
219
|
-
this.activityCursors = new Map();
|
|
220
|
-
this.signalCursors = new Map();
|
|
221
|
-
try {
|
|
222
|
-
await fs.unlink(this.persistPath);
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
// File may not exist — non-fatal
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
372
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @file Main Orchestrator for Sub-Agent Dispatch and Coordination.
|
|
3
3
|
*
|
|
4
4
|
* The orchestrator acts as the "PM Kernel", responsible for:
|
|
5
5
|
* 1. Task Decomposition (using gemini-3.7-flash)
|
|
@@ -13,10 +13,10 @@ export declare class Orchestrator {
|
|
|
13
13
|
private readonly workspaceRoot;
|
|
14
14
|
private readonly conversationId;
|
|
15
15
|
private readonly inputHandler;
|
|
16
|
+
private agentResults;
|
|
16
17
|
private bus;
|
|
17
18
|
private locks;
|
|
18
19
|
private pmChat;
|
|
19
|
-
private agentResults;
|
|
20
20
|
constructor(workspaceRoot: string, conversationId: string, inputHandler: AsyncInputHandler);
|
|
21
21
|
/**
|
|
22
22
|
* Main entry point for orchestration. Decomposes the task, validates the graph,
|
|
@@ -30,10 +30,6 @@ export declare class Orchestrator {
|
|
|
30
30
|
* Calls the PM Agent to decompose the task into a TaskGraph.
|
|
31
31
|
*/
|
|
32
32
|
private decomposeTask;
|
|
33
|
-
/**
|
|
34
|
-
* Computes execution waves and resolves file lock conflicts via pre-allocation.
|
|
35
|
-
*/
|
|
36
|
-
private scheduleWaves;
|
|
37
33
|
/**
|
|
38
34
|
* Dispatches a single sub-agent and records its result.
|
|
39
35
|
*/
|
|
@@ -42,4 +38,8 @@ export declare class Orchestrator {
|
|
|
42
38
|
* Final reconciliation phase after all waves complete.
|
|
43
39
|
*/
|
|
44
40
|
private reconcile;
|
|
41
|
+
/**
|
|
42
|
+
* Computes execution waves and resolves file lock conflicts via pre-allocation.
|
|
43
|
+
*/
|
|
44
|
+
private scheduleWaves;
|
|
45
45
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
2
|
+
* @file Main Orchestrator for Sub-Agent Dispatch and Coordination.
|
|
3
3
|
*
|
|
4
4
|
* The orchestrator acts as the "PM Kernel", responsible for:
|
|
5
5
|
* 1. Task Decomposition (using gemini-3.7-flash)
|
|
@@ -57,10 +57,10 @@ export class Orchestrator {
|
|
|
57
57
|
workspaceRoot;
|
|
58
58
|
conversationId;
|
|
59
59
|
inputHandler;
|
|
60
|
+
agentResults = new Map();
|
|
60
61
|
bus;
|
|
61
62
|
locks;
|
|
62
63
|
pmChat;
|
|
63
|
-
agentResults = new Map();
|
|
64
64
|
constructor(workspaceRoot, conversationId, inputHandler) {
|
|
65
65
|
this.workspaceRoot = workspaceRoot;
|
|
66
66
|
this.conversationId = conversationId;
|
|
@@ -103,7 +103,9 @@ export class Orchestrator {
|
|
|
103
103
|
if (signal.aborted)
|
|
104
104
|
break;
|
|
105
105
|
// Cooling-off pause before dispatching each execution wave
|
|
106
|
-
await new Promise((resolve) =>
|
|
106
|
+
await new Promise((resolve) => {
|
|
107
|
+
setTimeout(resolve, TPM_COOLING_DELAYS.ORCHESTRATION_WAVE_MS);
|
|
108
|
+
});
|
|
107
109
|
if (signal.aborted)
|
|
108
110
|
break;
|
|
109
111
|
const taskDescriptions = wave.taskIds
|
|
@@ -131,7 +133,9 @@ export class Orchestrator {
|
|
|
131
133
|
if (signal.aborted)
|
|
132
134
|
break;
|
|
133
135
|
if (i > 0) {
|
|
134
|
-
await new Promise((resolve) =>
|
|
136
|
+
await new Promise((resolve) => {
|
|
137
|
+
setTimeout(resolve, TPM_COOLING_DELAYS.PARALLEL_CHUNK_MS);
|
|
138
|
+
});
|
|
135
139
|
if (signal.aborted)
|
|
136
140
|
break;
|
|
137
141
|
}
|
|
@@ -163,7 +167,8 @@ export class Orchestrator {
|
|
|
163
167
|
const successful = results.filter((r) => r.success).length;
|
|
164
168
|
s.stop(`Wave ${wave.depth + 1} completed: ${successful}/${wave.taskIds.length} tasks succeeded`);
|
|
165
169
|
if (toolLogs.length > 0) {
|
|
166
|
-
|
|
170
|
+
for (const log of toolLogs)
|
|
171
|
+
p.log.step(log);
|
|
167
172
|
}
|
|
168
173
|
// Post-wave evaluation
|
|
169
174
|
const failedCount = results.filter((r) => !r.success).length;
|
|
@@ -179,7 +184,9 @@ export class Orchestrator {
|
|
|
179
184
|
}
|
|
180
185
|
// 4. Reconciliation
|
|
181
186
|
if (!signal.aborted) {
|
|
182
|
-
await new Promise((resolve) =>
|
|
187
|
+
await new Promise((resolve) => {
|
|
188
|
+
setTimeout(resolve, TPM_COOLING_DELAYS.ORCHESTRATION_RECONCILE_MS);
|
|
189
|
+
});
|
|
183
190
|
}
|
|
184
191
|
const finalSummary = await this.reconcile(graph, signal);
|
|
185
192
|
return finalSummary;
|
|
@@ -228,26 +235,16 @@ export class Orchestrator {
|
|
|
228
235
|
return null;
|
|
229
236
|
});
|
|
230
237
|
}
|
|
231
|
-
/**
|
|
232
|
-
* Computes execution waves and resolves file lock conflicts via pre-allocation.
|
|
233
|
-
*/
|
|
234
|
-
scheduleWaves(graph) {
|
|
235
|
-
try {
|
|
236
|
-
const rawWaves = computeExecutionWaves(graph);
|
|
237
|
-
const conflicts = detectFileConflicts(graph, rawWaves);
|
|
238
|
-
return resolveFileConflicts(rawWaves, conflicts);
|
|
239
|
-
}
|
|
240
|
-
catch (err) {
|
|
241
|
-
debugLog(`Orchestrator: Failed to schedule waves: ${err.message}`);
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
238
|
/**
|
|
246
239
|
* Dispatches a single sub-agent and records its result.
|
|
247
240
|
*/
|
|
248
241
|
async dispatchAgent(taskDef, globalContext, signal, onProgress, onTool) {
|
|
249
242
|
return trackTask(`Sub-Agent: ${taskDef.id}`, async () => {
|
|
250
|
-
const runner = new SubAgentRunner(taskDef.id, taskDef.intent, this.workspaceRoot, this.bus, this.locks, globalContext, onProgress, onTool
|
|
243
|
+
const runner = new SubAgentRunner(taskDef.id, taskDef.intent, this.workspaceRoot, this.bus, this.locks, globalContext, onProgress, onTool, {
|
|
244
|
+
targetFiles: taskDef.targetFiles,
|
|
245
|
+
readOnlyFiles: taskDef.readOnlyFiles,
|
|
246
|
+
dependsOn: taskDef.dependsOn,
|
|
247
|
+
});
|
|
251
248
|
const result = await runner.execute(signal);
|
|
252
249
|
// Clean up any stray locks if the agent crashed or stalled
|
|
253
250
|
if (result.crashed) {
|
|
@@ -310,4 +307,18 @@ export class Orchestrator {
|
|
|
310
307
|
this.locks.shutdown();
|
|
311
308
|
return finalSummary.trim();
|
|
312
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Computes execution waves and resolves file lock conflicts via pre-allocation.
|
|
312
|
+
*/
|
|
313
|
+
scheduleWaves(graph) {
|
|
314
|
+
try {
|
|
315
|
+
const rawWaves = computeExecutionWaves(graph);
|
|
316
|
+
const conflicts = detectFileConflicts(graph, rawWaves);
|
|
317
|
+
return resolveFileConflicts(rawWaves, conflicts);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
debugLog(`Orchestrator: Failed to schedule waves: ${err.message}`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
313
324
|
}
|
|
@@ -11,6 +11,11 @@ import { type PathResolutionOptions } from '../../utils/pathSecurity.js';
|
|
|
11
11
|
* @returns Absolute canonical file path
|
|
12
12
|
*/
|
|
13
13
|
export declare function resolveCanonicalLockPath(workspaceRoot: string, filePath: string, options?: PathResolutionOptions): string;
|
|
14
|
+
export interface TaskScope {
|
|
15
|
+
targetFiles?: string[];
|
|
16
|
+
readOnlyFiles?: string[];
|
|
17
|
+
dependsOn?: string[];
|
|
18
|
+
}
|
|
14
19
|
/**
|
|
15
20
|
* Returns Gemini tool declarations available to sub-agents during orchestration.
|
|
16
21
|
* In addition to standard agent tools (read/write/search/command/etc.), includes
|
|
@@ -30,5 +35,6 @@ export declare function getScopedToolDeclarations(): any[];
|
|
|
30
35
|
* @param locks - Shared file lock registry instance
|
|
31
36
|
* @param onProgress - Callback to notify parent of sub-agent progress
|
|
32
37
|
* @param options - Optional sub-path auto-focus and override configuration
|
|
38
|
+
* @param taskScope - Optional task scope metadata (targetFiles, dependsOn)
|
|
33
39
|
*/
|
|
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>;
|
|
40
|
+
export declare function executeScopedTool(name: string, args: Record<string, any>, workspaceRoot: string, agentId: string, bus: MessageBus, locks: FileLockRegistry, onProgress: (msg?: string) => void, options?: PathResolutionOptions, taskScope?: TaskScope): Promise<any>;
|