minovative-mind-cli 2.13.5 → 2.14.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 +102 -39
- package/dist/services/agent/slashCommands.js +47 -7
- package/dist/services/agent/syntaxAgent.js +13 -0
- package/dist/services/agent-tools.d.ts +1 -1
- package/dist/services/agent-tools.js +2 -2
- package/dist/services/agent.js +119 -7
- package/dist/services/ai.d.ts +19 -0
- package/dist/services/ai.js +96 -5
- package/dist/services/contextAgent.js +23 -0
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/mentionEngine.d.ts +385 -0
- package/dist/services/mentionEngine.js +1395 -0
- package/dist/services/orchestration/investigationAgent.js +4 -1
- package/dist/services/orchestration/messageBus.d.ts +26 -3
- package/dist/services/orchestration/messageBus.js +204 -14
- package/dist/services/orchestration/orchestrator.js +4 -0
- package/dist/services/orchestration/scopedTools.js +16 -2
- package/dist/services/orchestration/subAgent.js +14 -2
- package/dist/services/proxyClient.d.ts +38 -2
- package/dist/services/proxyClient.js +42 -24
- package/dist/utils/config.d.ts +75 -0
- package/dist/utils/config.js +93 -0
- package/dist/utils/contextPrompts.d.ts +28 -4
- package/dist/utils/contextPrompts.js +70 -1
- package/dist/utils/historyPrompt.d.ts +166 -7
- package/dist/utils/historyPrompt.js +775 -30
- package/dist/utils/symbolExtractor.d.ts +111 -8
- package/dist/utils/symbolExtractor.js +616 -64
- package/dist/utils/systemPrompts.d.ts +3 -3
- package/dist/utils/systemPrompts.js +5 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -59,6 +59,9 @@ export class InvestigationAgentRunner {
|
|
|
59
59
|
temperature: 1,
|
|
60
60
|
topP: 0.95,
|
|
61
61
|
topK: 40,
|
|
62
|
+
thinkingConfig: {
|
|
63
|
+
thinkingLevel: 'MEDIUM',
|
|
64
|
+
},
|
|
62
65
|
}, {
|
|
63
66
|
functionCallingConfig: {
|
|
64
67
|
mode: 'ANY',
|
|
@@ -98,7 +101,7 @@ export class InvestigationAgentRunner {
|
|
|
98
101
|
* @returns The investigation result with discovered files and summary.
|
|
99
102
|
*/
|
|
100
103
|
async execute(userRequest, chatHistory, abortSignal, onProgress, onTool) {
|
|
101
|
-
debugLog(`InvestigationAgent [${this.agentLabel}]: Starting execution for domains: ${this.domains.join(', ')}`);
|
|
104
|
+
debugLog(`InvestigationAgent [${this.agentLabel}]: Starting execution [Model: ${this.chat.getModel()}, Thinking: ${this.chat.getThinkingLevel() || 'MEDIUM'}] for domains: ${this.domains.join(', ')}`);
|
|
102
105
|
this.lastHeartbeat = Date.now();
|
|
103
106
|
const relevantFiles = new Map();
|
|
104
107
|
let summary = 'No relevant context found.';
|
|
@@ -76,9 +76,15 @@ export interface BusQueryOptions {
|
|
|
76
76
|
type?: 'discovery' | 'warning' | 'request' | 'completion';
|
|
77
77
|
onlyMutations?: boolean;
|
|
78
78
|
targetFiles?: string[];
|
|
79
|
+
readOnlyFiles?: string[];
|
|
79
80
|
dependsOn?: string[];
|
|
80
81
|
advanceCursor?: boolean;
|
|
81
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Checks if a target path matches any file in a scoped file list,
|
|
85
|
+
* handling relative paths, normalizations, and partial segment matches.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isPathMatchingScope(target: string, scopedFiles: string[]): boolean;
|
|
82
88
|
interface CoalescedEntry {
|
|
83
89
|
agentId: string;
|
|
84
90
|
tool: string;
|
|
@@ -104,6 +110,7 @@ export declare class MessageBus {
|
|
|
104
110
|
static readonly MAX_SIGNALS_PER_AGENT = 1000;
|
|
105
111
|
private activityCursors;
|
|
106
112
|
private activityLog;
|
|
113
|
+
private inBandNoticeCursors;
|
|
107
114
|
private readonly persistPath;
|
|
108
115
|
private persistQueue;
|
|
109
116
|
private signalCursors;
|
|
@@ -117,6 +124,7 @@ export declare class MessageBus {
|
|
|
117
124
|
static formatActivityEntries(entries: ActivityEntry[]): string;
|
|
118
125
|
/**
|
|
119
126
|
* Formats semantic signals into a readable string for agent context injection.
|
|
127
|
+
* Deduplicates identical signal renderings to eliminate payload bloat.
|
|
120
128
|
*/
|
|
121
129
|
static formatSignals(signals: BusSignal[]): string;
|
|
122
130
|
/**
|
|
@@ -159,22 +167,36 @@ export declare class MessageBus {
|
|
|
159
167
|
/**
|
|
160
168
|
* Records a tool execution into the activity log. Called by the scoped tool
|
|
161
169
|
* wrapper in `scopedTools.ts` — zero cost to the agent.
|
|
170
|
+
* Sanitizes and caps result summaries and target paths to eliminate payload bloat.
|
|
162
171
|
*/
|
|
163
172
|
logActivity(entry: ActivityEntry): void;
|
|
173
|
+
/**
|
|
174
|
+
* Retrieves newly arrived urgent signals relevant to an agent's task scope
|
|
175
|
+
* that have NOT yet been delivered in-band to this agent, and advances the in-band cursor.
|
|
176
|
+
* This ensures an urgent notice is delivered ONCE on the next tool execution
|
|
177
|
+
* rather than being repetitively injected on every single subsequent tool turn.
|
|
178
|
+
*/
|
|
179
|
+
getNewUrgentSignals(agentId: string, scope?: {
|
|
180
|
+
targetFiles?: string[];
|
|
181
|
+
readOnlyFiles?: string[];
|
|
182
|
+
dependsOn?: string[];
|
|
183
|
+
}): BusSignal[];
|
|
164
184
|
/**
|
|
165
185
|
* Peeks urgent signals (breaking warnings or direct requests) that are relevant
|
|
166
186
|
* to a sub-agent's task scope without advancing its cursor. Used for in-band notice
|
|
167
|
-
* delivery
|
|
187
|
+
* delivery or diagnostics.
|
|
168
188
|
*/
|
|
169
189
|
peekUrgentSignals(agentId: string, scope?: {
|
|
170
190
|
targetFiles?: string[];
|
|
191
|
+
readOnlyFiles?: string[];
|
|
171
192
|
dependsOn?: string[];
|
|
172
193
|
}): BusSignal[];
|
|
173
194
|
/**
|
|
174
195
|
* Posts a semantic signal from an agent. These cost tokens (the agent calls
|
|
175
196
|
* `post_message`) but carry intent that raw tool logs cannot express.
|
|
176
197
|
*
|
|
177
|
-
* Enforces
|
|
198
|
+
* Enforces signal deduplication, content sanitization, and per-agent caps
|
|
199
|
+
* to eliminate redundant message propagation and bus flooding.
|
|
178
200
|
*
|
|
179
201
|
* @returns `true` if the signal was accepted, `false` if the agent hit the cap.
|
|
180
202
|
*/
|
|
@@ -182,7 +204,8 @@ export declare class MessageBus {
|
|
|
182
204
|
/**
|
|
183
205
|
* Performs an intelligent, scoped query against unread bus activity and signals.
|
|
184
206
|
* Filters out read-only noise for peer sub-agents, enforces unicast delivery for targeted
|
|
185
|
-
* requests, and
|
|
207
|
+
* requests, applies task-scope and dependency filtering to eliminate payload bloat,
|
|
208
|
+
* and supports selective querying by file, agent, or signal type.
|
|
186
209
|
*/
|
|
187
210
|
queryBus(options: BusQueryOptions): {
|
|
188
211
|
activities: ActivityEntry[];
|
|
@@ -20,6 +20,23 @@ import path from 'node:path';
|
|
|
20
20
|
import { atomicWriteFile } from '../../utils/atomicWrite.js';
|
|
21
21
|
import { debugLog } from '../../utils/logger.js';
|
|
22
22
|
export const MUTATING_TOOLS = new Set(['write_file', 'modify_file', 'delete_file', 'rename_file', 'run_command']);
|
|
23
|
+
/**
|
|
24
|
+
* Checks if a target path matches any file in a scoped file list,
|
|
25
|
+
* handling relative paths, normalizations, and partial segment matches.
|
|
26
|
+
*/
|
|
27
|
+
export function isPathMatchingScope(target, scopedFiles) {
|
|
28
|
+
if (!target || !scopedFiles || scopedFiles.length === 0)
|
|
29
|
+
return false;
|
|
30
|
+
const normTarget = target.replace(/\\/g, '/').toLowerCase().replace(/^\.\//, '');
|
|
31
|
+
return scopedFiles.some((f) => {
|
|
32
|
+
const normScope = f.replace(/\\/g, '/').toLowerCase().replace(/^\.\//, '');
|
|
33
|
+
return (normTarget === normScope ||
|
|
34
|
+
normTarget.endsWith(`/${normScope}`) ||
|
|
35
|
+
normScope.endsWith(`/${normTarget}`) ||
|
|
36
|
+
normTarget.includes(normScope) ||
|
|
37
|
+
normScope.includes(normTarget));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
23
40
|
export function coalesceActivityEntries(entries) {
|
|
24
41
|
const result = [];
|
|
25
42
|
for (const entry of entries) {
|
|
@@ -61,6 +78,7 @@ export class MessageBus {
|
|
|
61
78
|
static MAX_SIGNALS_PER_AGENT = 1000;
|
|
62
79
|
activityCursors = new Map();
|
|
63
80
|
activityLog = [];
|
|
81
|
+
inBandNoticeCursors = new Map();
|
|
64
82
|
persistPath;
|
|
65
83
|
persistQueue = Promise.resolve();
|
|
66
84
|
signalCursors = new Map();
|
|
@@ -93,27 +111,39 @@ export class MessageBus {
|
|
|
93
111
|
// ─── Layer 2: Semantic Signals ───────────────────────────────────
|
|
94
112
|
/**
|
|
95
113
|
* Formats semantic signals into a readable string for agent context injection.
|
|
114
|
+
* Deduplicates identical signal renderings to eliminate payload bloat.
|
|
96
115
|
*/
|
|
97
116
|
static formatSignals(signals) {
|
|
98
117
|
if (signals.length === 0)
|
|
99
118
|
return '';
|
|
100
|
-
|
|
101
|
-
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
const lines = [];
|
|
121
|
+
for (const s of signals) {
|
|
102
122
|
const tag = s.type.toUpperCase();
|
|
123
|
+
let line = '';
|
|
103
124
|
switch (s.type) {
|
|
104
125
|
case 'discovery':
|
|
105
|
-
|
|
126
|
+
line = ` [${tag} from ${s.fromAgent}]: "${s.content}" (affects: ${s.affectedFiles.join(', ')})`;
|
|
127
|
+
break;
|
|
106
128
|
case 'warning':
|
|
107
|
-
|
|
129
|
+
line = ` [${tag} from ${s.fromAgent}]: "${s.content}"${s.changedSignature ? ` (signature: ${s.changedSignature})` : ''}`;
|
|
130
|
+
break;
|
|
108
131
|
case 'request':
|
|
109
|
-
|
|
132
|
+
line = ` [${tag} from ${s.fromAgent} → ${s.toAgent}]: "${s.content}"`;
|
|
133
|
+
break;
|
|
110
134
|
case 'completion':
|
|
111
|
-
|
|
135
|
+
line = ` [${tag} from ${s.fromAgent}]: "${s.summary}"`;
|
|
136
|
+
break;
|
|
112
137
|
default:
|
|
113
|
-
|
|
138
|
+
line = ` [SIGNAL from ${s.fromAgent}]: ${JSON.stringify(s)}`;
|
|
139
|
+
break;
|
|
114
140
|
}
|
|
115
|
-
|
|
116
|
-
|
|
141
|
+
if (!seen.has(line)) {
|
|
142
|
+
seen.add(line);
|
|
143
|
+
lines.push(line);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
117
147
|
}
|
|
118
148
|
// ─── Reading ─────────────────────────────────────────────────────
|
|
119
149
|
/**
|
|
@@ -125,6 +155,7 @@ export class MessageBus {
|
|
|
125
155
|
this.signals = [];
|
|
126
156
|
this.activityCursors = new Map();
|
|
127
157
|
this.signalCursors = new Map();
|
|
158
|
+
this.inBandNoticeCursors = new Map();
|
|
128
159
|
try {
|
|
129
160
|
await fs.unlink(this.persistPath);
|
|
130
161
|
}
|
|
@@ -173,21 +204,76 @@ export class MessageBus {
|
|
|
173
204
|
/**
|
|
174
205
|
* Records a tool execution into the activity log. Called by the scoped tool
|
|
175
206
|
* wrapper in `scopedTools.ts` — zero cost to the agent.
|
|
207
|
+
* Sanitizes and caps result summaries and target paths to eliminate payload bloat.
|
|
176
208
|
*/
|
|
177
209
|
logActivity(entry) {
|
|
210
|
+
if (entry.resultSummary && entry.resultSummary.length > 120) {
|
|
211
|
+
entry.resultSummary = entry.resultSummary.substring(0, 120) + '...';
|
|
212
|
+
}
|
|
213
|
+
if (entry.target && entry.target.length > 300) {
|
|
214
|
+
entry.target = entry.target.substring(0, 300) + '...';
|
|
215
|
+
}
|
|
178
216
|
this.activityLog.push(entry);
|
|
179
217
|
this.persistToDiskAsync();
|
|
180
218
|
}
|
|
181
219
|
// ─── Formatting Helpers ──────────────────────────────────────────
|
|
220
|
+
/**
|
|
221
|
+
* Retrieves newly arrived urgent signals relevant to an agent's task scope
|
|
222
|
+
* that have NOT yet been delivered in-band to this agent, and advances the in-band cursor.
|
|
223
|
+
* This ensures an urgent notice is delivered ONCE on the next tool execution
|
|
224
|
+
* rather than being repetitively injected on every single subsequent tool turn.
|
|
225
|
+
*/
|
|
226
|
+
getNewUrgentSignals(agentId, scope) {
|
|
227
|
+
const cursor = this.inBandNoticeCursors.get(agentId) ?? 0;
|
|
228
|
+
if (cursor >= this.signals.length)
|
|
229
|
+
return [];
|
|
230
|
+
const newSignals = this.signals.slice(cursor).filter((s) => s.fromAgent !== agentId);
|
|
231
|
+
const urgent = [];
|
|
232
|
+
const scopedFiles = [...(scope?.targetFiles || []), ...(scope?.readOnlyFiles || [])];
|
|
233
|
+
for (const signal of newSignals) {
|
|
234
|
+
// 1. Direct targeted request to this agent
|
|
235
|
+
if (signal.type === 'request' && (signal.toAgent === agentId || signal.toAgent === 'all')) {
|
|
236
|
+
urgent.push(signal);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
// 2. Warnings from upstream dependencies or affecting scoped files
|
|
240
|
+
if (signal.type === 'warning') {
|
|
241
|
+
if (!scope?.dependsOn || scope.dependsOn.length === 0 || scope.dependsOn.includes(signal.fromAgent)) {
|
|
242
|
+
urgent.push(signal);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (scopedFiles.length > 0) {
|
|
246
|
+
const warn = signal;
|
|
247
|
+
const touches = scopedFiles.some((f) => {
|
|
248
|
+
const normF = f.replace(/\\/g, '/').toLowerCase();
|
|
249
|
+
return (warn.content.toLowerCase().includes(normF) ||
|
|
250
|
+
Boolean(warn.changedSignature && warn.changedSignature.toLowerCase().includes(normF)));
|
|
251
|
+
});
|
|
252
|
+
if (touches) {
|
|
253
|
+
urgent.push(signal);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// 3. Upstream completion signals from direct dependencies
|
|
259
|
+
if (signal.type === 'completion' && scope?.dependsOn && scope.dependsOn.includes(signal.fromAgent)) {
|
|
260
|
+
urgent.push(signal);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
this.inBandNoticeCursors.set(agentId, this.signals.length);
|
|
265
|
+
return urgent;
|
|
266
|
+
}
|
|
182
267
|
/**
|
|
183
268
|
* Peeks urgent signals (breaking warnings or direct requests) that are relevant
|
|
184
269
|
* to a sub-agent's task scope without advancing its cursor. Used for in-band notice
|
|
185
|
-
* delivery
|
|
270
|
+
* delivery or diagnostics.
|
|
186
271
|
*/
|
|
187
272
|
peekUrgentSignals(agentId, scope) {
|
|
188
273
|
const sigCursor = this.signalCursors.get(agentId) ?? 0;
|
|
189
274
|
const unreadSignals = this.signals.slice(sigCursor).filter((s) => s.fromAgent !== agentId);
|
|
190
275
|
const urgent = [];
|
|
276
|
+
const scopedFiles = [...(scope?.targetFiles || []), ...(scope?.readOnlyFiles || [])];
|
|
191
277
|
for (const signal of unreadSignals) {
|
|
192
278
|
// 1. Direct targeted request to this agent
|
|
193
279
|
if (signal.type === 'request' && (signal.toAgent === agentId || signal.toAgent === 'all')) {
|
|
@@ -196,10 +282,22 @@ export class MessageBus {
|
|
|
196
282
|
}
|
|
197
283
|
// 2. Warnings from upstream dependencies or affecting target files
|
|
198
284
|
if (signal.type === 'warning') {
|
|
199
|
-
if (!scope || !scope.dependsOn || scope.dependsOn.includes(signal.fromAgent)) {
|
|
285
|
+
if (!scope || !scope.dependsOn || scope.dependsOn.length === 0 || scope.dependsOn.includes(signal.fromAgent)) {
|
|
200
286
|
urgent.push(signal);
|
|
201
287
|
continue;
|
|
202
288
|
}
|
|
289
|
+
if (scopedFiles.length > 0) {
|
|
290
|
+
const warn = signal;
|
|
291
|
+
const touches = scopedFiles.some((f) => {
|
|
292
|
+
const normF = f.replace(/\\/g, '/').toLowerCase();
|
|
293
|
+
return (warn.content.toLowerCase().includes(normF) ||
|
|
294
|
+
Boolean(warn.changedSignature && warn.changedSignature.toLowerCase().includes(normF)));
|
|
295
|
+
});
|
|
296
|
+
if (touches) {
|
|
297
|
+
urgent.push(signal);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
203
301
|
}
|
|
204
302
|
// 3. Upstream completion signals from direct dependencies
|
|
205
303
|
if (signal.type === 'completion' && scope?.dependsOn && scope.dependsOn.includes(signal.fromAgent)) {
|
|
@@ -213,11 +311,53 @@ export class MessageBus {
|
|
|
213
311
|
* Posts a semantic signal from an agent. These cost tokens (the agent calls
|
|
214
312
|
* `post_message`) but carry intent that raw tool logs cannot express.
|
|
215
313
|
*
|
|
216
|
-
* Enforces
|
|
314
|
+
* Enforces signal deduplication, content sanitization, and per-agent caps
|
|
315
|
+
* to eliminate redundant message propagation and bus flooding.
|
|
217
316
|
*
|
|
218
317
|
* @returns `true` if the signal was accepted, `false` if the agent hit the cap.
|
|
219
318
|
*/
|
|
220
319
|
postSignal(signal) {
|
|
320
|
+
if (signal.type === 'completion') {
|
|
321
|
+
if (signal.summary) {
|
|
322
|
+
signal.summary = signal.summary.trim();
|
|
323
|
+
if (signal.summary.length > 500) {
|
|
324
|
+
signal.summary = signal.summary.substring(0, 500) + '... (truncated)';
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else if (signal.content) {
|
|
329
|
+
signal.content = signal.content.trim();
|
|
330
|
+
if (signal.content.length > 500) {
|
|
331
|
+
signal.content = signal.content.substring(0, 500) + '... (truncated)';
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// Deduplication check: suppress identical duplicate signals posted recently
|
|
335
|
+
const isDuplicate = this.signals.slice(-10).some((s) => {
|
|
336
|
+
if (s.type !== signal.type || s.fromAgent !== signal.fromAgent) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
if (s.type === 'completion' && signal.type === 'completion') {
|
|
340
|
+
return s.summary === signal.summary;
|
|
341
|
+
}
|
|
342
|
+
if (s.type === 'discovery' && signal.type === 'discovery') {
|
|
343
|
+
if (s.content !== signal.content)
|
|
344
|
+
return false;
|
|
345
|
+
const sFiles = (s.affectedFiles || []).slice().sort().join(',');
|
|
346
|
+
const sigFiles = (signal.affectedFiles || []).slice().sort().join(',');
|
|
347
|
+
return sFiles === sigFiles;
|
|
348
|
+
}
|
|
349
|
+
if (s.type === 'request' && signal.type === 'request') {
|
|
350
|
+
return s.content === signal.content && s.toAgent === signal.toAgent;
|
|
351
|
+
}
|
|
352
|
+
if (s.type === 'warning' && signal.type === 'warning') {
|
|
353
|
+
return s.content === signal.content && s.changedSignature === signal.changedSignature;
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
});
|
|
357
|
+
if (isDuplicate) {
|
|
358
|
+
debugLog(`MessageBus: Suppressed duplicate ${signal.type} signal from ${signal.fromAgent}.`);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
221
361
|
const agentSignalCount = this.signals.filter((s) => s.fromAgent === signal.fromAgent).length;
|
|
222
362
|
if (agentSignalCount >= MessageBus.MAX_SIGNALS_PER_AGENT) {
|
|
223
363
|
debugLog(`MessageBus: Agent ${signal.fromAgent} hit signal cap (${MessageBus.MAX_SIGNALS_PER_AGENT}). ` +
|
|
@@ -232,13 +372,16 @@ export class MessageBus {
|
|
|
232
372
|
/**
|
|
233
373
|
* Performs an intelligent, scoped query against unread bus activity and signals.
|
|
234
374
|
* Filters out read-only noise for peer sub-agents, enforces unicast delivery for targeted
|
|
235
|
-
* requests, and
|
|
375
|
+
* requests, applies task-scope and dependency filtering to eliminate payload bloat,
|
|
376
|
+
* and supports selective querying by file, agent, or signal type.
|
|
236
377
|
*/
|
|
237
378
|
queryBus(options) {
|
|
238
379
|
const actCursor = this.activityCursors.get(options.agentId) ?? 0;
|
|
239
380
|
const sigCursor = this.signalCursors.get(options.agentId) ?? 0;
|
|
240
381
|
const onlyMutations = options.onlyMutations ?? true;
|
|
241
382
|
const normalizedFile = options.file ? options.file.replace(/\\/g, '/').toLowerCase() : undefined;
|
|
383
|
+
const scopedFiles = [...(options.targetFiles || []), ...(options.readOnlyFiles || [])];
|
|
384
|
+
const hasScopeConstraint = !normalizedFile && (scopedFiles.length > 0 || (options.dependsOn && options.dependsOn.length > 0));
|
|
242
385
|
const activities = this.activityLog.slice(actCursor).filter((e) => {
|
|
243
386
|
// Exclude own actions
|
|
244
387
|
if (e.agentId === options.agentId)
|
|
@@ -246,13 +389,21 @@ export class MessageBus {
|
|
|
246
389
|
// Filter read-only tools if onlyMutations is enabled
|
|
247
390
|
if (onlyMutations && !MUTATING_TOOLS.has(e.tool))
|
|
248
391
|
return false;
|
|
249
|
-
// Optional file filter
|
|
392
|
+
// Optional explicit file filter
|
|
250
393
|
if (normalizedFile) {
|
|
251
394
|
const entryTarget = e.target.replace(/\\/g, '/').toLowerCase();
|
|
252
395
|
if (!entryTarget.includes(normalizedFile) && !normalizedFile.includes(entryTarget)) {
|
|
253
396
|
return false;
|
|
254
397
|
}
|
|
255
398
|
}
|
|
399
|
+
else if (hasScopeConstraint) {
|
|
400
|
+
// Filter by task scope (target/readOnly files or upstream dependencies)
|
|
401
|
+
const matchesFile = scopedFiles.length > 0 && isPathMatchingScope(e.target, scopedFiles);
|
|
402
|
+
const matchesDep = Boolean(options.dependsOn && options.dependsOn.includes(e.agentId));
|
|
403
|
+
if (!matchesFile && !matchesDep) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
256
407
|
// Optional fromAgent filter
|
|
257
408
|
if (options.fromAgent && e.agentId !== options.fromAgent)
|
|
258
409
|
return false;
|
|
@@ -306,6 +457,45 @@ export class MessageBus {
|
|
|
306
457
|
return false;
|
|
307
458
|
}
|
|
308
459
|
}
|
|
460
|
+
else if (hasScopeConstraint) {
|
|
461
|
+
// Scoped signals filtering
|
|
462
|
+
if (s.type === 'request') {
|
|
463
|
+
// Direct or broadcast requests are always relevant
|
|
464
|
+
}
|
|
465
|
+
else if (s.type === 'warning') {
|
|
466
|
+
const isFromDep = Boolean(options.dependsOn && options.dependsOn.includes(s.fromAgent));
|
|
467
|
+
const touchesScopedFile = scopedFiles.length > 0 &&
|
|
468
|
+
scopedFiles.some((f) => {
|
|
469
|
+
const normF = f.replace(/\\/g, '/').toLowerCase();
|
|
470
|
+
return (s.content.toLowerCase().includes(normF) ||
|
|
471
|
+
Boolean(s.changedSignature &&
|
|
472
|
+
s.changedSignature.toLowerCase().includes(normF)));
|
|
473
|
+
});
|
|
474
|
+
const isBroadcast = !options.dependsOn || options.dependsOn.length === 0;
|
|
475
|
+
if (!isFromDep && !touchesScopedFile && !isBroadcast)
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
else if (s.type === 'completion') {
|
|
479
|
+
const isFromDep = Boolean(options.dependsOn && options.dependsOn.includes(s.fromAgent));
|
|
480
|
+
const touchesScopedFile = scopedFiles.length > 0 &&
|
|
481
|
+
scopedFiles.some((f) => {
|
|
482
|
+
const normF = f.replace(/\\/g, '/').toLowerCase();
|
|
483
|
+
return (s.summary.toLowerCase().includes(normF) ||
|
|
484
|
+
Object.keys(s.exports || {}).some((k) => k.toLowerCase().includes(normF)));
|
|
485
|
+
});
|
|
486
|
+
if (!isFromDep && !touchesScopedFile)
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
else if (s.type === 'discovery') {
|
|
490
|
+
const disc = s;
|
|
491
|
+
const isFromDep = Boolean(options.dependsOn && options.dependsOn.includes(s.fromAgent));
|
|
492
|
+
const touchesScopedFile = scopedFiles.length > 0 &&
|
|
493
|
+
((disc.affectedFiles || []).some((f) => isPathMatchingScope(f, scopedFiles)) ||
|
|
494
|
+
scopedFiles.some((f) => disc.content.toLowerCase().includes(f.toLowerCase())));
|
|
495
|
+
if (!isFromDep && !touchesScopedFile)
|
|
496
|
+
return false;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
309
499
|
return true;
|
|
310
500
|
});
|
|
311
501
|
// Advance cursors if requested (default: true)
|
|
@@ -74,6 +74,9 @@ export class Orchestrator {
|
|
|
74
74
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
75
75
|
temperature: 0.1,
|
|
76
76
|
responseMimeType: 'application/json',
|
|
77
|
+
thinkingConfig: {
|
|
78
|
+
thinkingLevel: 'HIGH',
|
|
79
|
+
},
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
/**
|
|
@@ -196,6 +199,7 @@ export class Orchestrator {
|
|
|
196
199
|
*/
|
|
197
200
|
async decomposeTask(objective, contextInjection, signal) {
|
|
198
201
|
return trackTask('PM Agent: Graph Generation', async () => {
|
|
202
|
+
debugLog(`Orchestrator: PM Agent decomposing task [Model: ${this.pmChat.getModel()}, Thinking: ${this.pmChat.getThinkingLevel() || 'HIGH'}]`);
|
|
199
203
|
const prompt = `Objective:\n${objective}\n\nContext:\n${contextInjection}`;
|
|
200
204
|
let result = await this.pmChat.sendMessage(prompt, undefined, signal);
|
|
201
205
|
if (signal.aborted)
|
|
@@ -141,6 +141,7 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
|
|
|
141
141
|
type: args.type,
|
|
142
142
|
onlyMutations: true,
|
|
143
143
|
targetFiles: taskScope?.targetFiles,
|
|
144
|
+
readOnlyFiles: taskScope?.readOnlyFiles,
|
|
144
145
|
dependsOn: taskScope?.dependsOn,
|
|
145
146
|
});
|
|
146
147
|
if (unread.activities.length === 0 && unread.signals.length === 0) {
|
|
@@ -235,7 +236,14 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
|
|
|
235
236
|
result._orchestrationWarning = `NOTICE: Another agent modified this file while you were waiting. Diff context: ${diffContext}`;
|
|
236
237
|
}
|
|
237
238
|
// In-band relevance-gated urgent signals (warnings, targeted requests, or upstream completions)
|
|
238
|
-
|
|
239
|
+
// Uses getNewUrgentSignals so notices are delivered once when they occur, avoiding redundant injection on every turn
|
|
240
|
+
if (typeof result === 'object' && result !== null && typeof bus?.getNewUrgentSignals === 'function') {
|
|
241
|
+
const urgentSignals = bus.getNewUrgentSignals(agentId, taskScope);
|
|
242
|
+
if (urgentSignals && urgentSignals.length > 0) {
|
|
243
|
+
result._orchestrationNotice = MessageBus.formatSignals(urgentSignals);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else if (typeof result === 'object' && result !== null && typeof bus?.peekUrgentSignals === 'function') {
|
|
239
247
|
const urgentSignals = bus.peekUrgentSignals(agentId, taskScope);
|
|
240
248
|
if (urgentSignals && urgentSignals.length > 0) {
|
|
241
249
|
result._orchestrationNotice = MessageBus.formatSignals(urgentSignals);
|
|
@@ -253,7 +261,13 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
|
|
|
253
261
|
}
|
|
254
262
|
else {
|
|
255
263
|
// For cache hit, also check urgent signals from bus if needed
|
|
256
|
-
if (typeof result === 'object' && result !== null && typeof bus?.
|
|
264
|
+
if (typeof result === 'object' && result !== null && typeof bus?.getNewUrgentSignals === 'function') {
|
|
265
|
+
const urgentSignals = bus.getNewUrgentSignals(agentId, taskScope);
|
|
266
|
+
if (urgentSignals && urgentSignals.length > 0) {
|
|
267
|
+
result._orchestrationNotice = MessageBus.formatSignals(urgentSignals);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else if (typeof result === 'object' && result !== null && typeof bus?.peekUrgentSignals === 'function') {
|
|
257
271
|
const urgentSignals = bus.peekUrgentSignals(agentId, taskScope);
|
|
258
272
|
if (urgentSignals && urgentSignals.length > 0) {
|
|
259
273
|
result._orchestrationNotice = MessageBus.formatSignals(urgentSignals);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Implements the lifecycle, health monitoring, and tool-loop execution for a single
|
|
5
5
|
* parallelized sub-agent.
|
|
6
6
|
*/
|
|
7
|
-
import { ProxyChatSession, getGlobalActiveModel } from '../ai.js';
|
|
7
|
+
import { ProxyChatSession, getGlobalActiveModel, getModelThinkingLevel } from '../ai.js';
|
|
8
8
|
import { GEMINI_MODELS, MAX_OUTPUT_TOKENS } from '../../utils/config.js';
|
|
9
9
|
import { ReadCache } from './readCache.js';
|
|
10
10
|
import { executeScopedTool, getScopedToolDeclarations } from './scopedTools.js';
|
|
@@ -165,12 +165,16 @@ export class SubAgentRunner {
|
|
|
165
165
|
let model = getGlobalActiveModel();
|
|
166
166
|
if (model === GEMINI_MODELS.AUTO)
|
|
167
167
|
model = GEMINI_MODELS.FLASH;
|
|
168
|
-
|
|
168
|
+
const thinkingLevel = getModelThinkingLevel(model);
|
|
169
|
+
// Sub-agents default to flash-3.7 for better reasoning capabilities with dynamic/model-configured thinking level
|
|
169
170
|
this.chat = new ProxyChatSession(model, this.buildSystemInstruction(), [{ functionDeclarations: getScopedToolDeclarations() }], {
|
|
170
171
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
171
172
|
temperature: 0.3, // Lower temperature for more focused execution
|
|
172
173
|
topP: 0.95,
|
|
173
174
|
topK: 40,
|
|
175
|
+
thinkingConfig: {
|
|
176
|
+
thinkingLevel,
|
|
177
|
+
},
|
|
174
178
|
}, {
|
|
175
179
|
functionCallingConfig: {
|
|
176
180
|
mode: 'ANY',
|
|
@@ -204,6 +208,13 @@ export class SubAgentRunner {
|
|
|
204
208
|
const args = matchingCall?.args;
|
|
205
209
|
if (funcResp.response && typeof funcResp.response === 'object') {
|
|
206
210
|
const respObj = funcResp.response;
|
|
211
|
+
// Clean up historical orchestration notices and warnings to eliminate payload bloat in history
|
|
212
|
+
if (respObj._orchestrationNotice) {
|
|
213
|
+
delete respObj._orchestrationNotice;
|
|
214
|
+
}
|
|
215
|
+
if (respObj._orchestrationWarning) {
|
|
216
|
+
delete respObj._orchestrationWarning;
|
|
217
|
+
}
|
|
207
218
|
if (toolName === 'read_messages') {
|
|
208
219
|
funcResp.response = { output: '[read_messages - peer activity reviewed]' };
|
|
209
220
|
}
|
|
@@ -248,6 +259,7 @@ export class SubAgentRunner {
|
|
|
248
259
|
}
|
|
249
260
|
}, 5000);
|
|
250
261
|
try {
|
|
262
|
+
debugLog(`SubAgent [${this.taskId}]: Starting execution [Model: ${this.chat.getModel()}, Thinking: ${this.chat.getThinkingLevel() || 'DEFAULT'}]`);
|
|
251
263
|
// Send initial prompt
|
|
252
264
|
const prompt = `Begin execution for task: ${this.taskId}\nObjective: ${this.intent}\n\nFollow the 3-step workflow: 1) inspect file -> 2) modify code -> 3) respond with text summary. Do not repeat search tools once results are returned.`;
|
|
253
265
|
let turnResult = await this.chat.sendMessage(prompt, undefined, signal);
|
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
import type { Content, Tool, ToolConfig, FunctionCall } from '@google/generative-ai';
|
|
2
|
+
import { type ThinkingConfig } from '../utils/config.js';
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* PROXY CLIENT SERVICE
|
|
6
|
+
* ============================================================================
|
|
7
|
+
* Facilitates stream-based communication with the secure, Firebase-authenticated
|
|
8
|
+
* serverless Gemini content generation proxy.
|
|
9
|
+
*
|
|
10
|
+
* Core Capabilities:
|
|
11
|
+
* - Server-Sent Events (SSE) parsing for thoughts, text, and function calls.
|
|
12
|
+
* - Secure Authentication handling (Firebase ID Tokens).
|
|
13
|
+
* - Real-time stream-callback piping for instant responses.
|
|
14
|
+
* - Accurate token usage, credit consumption, and grounding metadata parsing.
|
|
15
|
+
* - Dynamic ThinkingConfig / ThinkingLevel reasoning configuration pass-through.
|
|
16
|
+
* ============================================================================
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Generation configuration passed to Proxy and BYOK endpoints.
|
|
20
|
+
*/
|
|
21
|
+
export interface ProxyGenerationConfig {
|
|
22
|
+
temperature?: number;
|
|
23
|
+
topP?: number;
|
|
24
|
+
topK?: number;
|
|
25
|
+
maxOutputTokens?: number;
|
|
26
|
+
responseMimeType?: string;
|
|
27
|
+
responseSchema?: any;
|
|
28
|
+
thinkingConfig?: ThinkingConfig;
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Normalizes generation configuration across Proxy and BYOK endpoints.
|
|
33
|
+
* Strips 'MINIMAL' thinking level since Gemini 3.x models use default thinking
|
|
34
|
+
* behavior when no explicit thinking level is provided, preventing 400 Bad Request
|
|
35
|
+
* rejections from Google's Generative Language API.
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeGenerationConfig(config?: ProxyGenerationConfig): ProxyGenerationConfig | undefined;
|
|
2
38
|
/**
|
|
3
39
|
* Metadata containing real-time proxy token and credit usage diagnostics.
|
|
4
40
|
*/
|
|
@@ -62,7 +98,7 @@ export declare class ProxyClient {
|
|
|
62
98
|
* @returns A structured promise resolving to gathered text, function calls, and metadata.
|
|
63
99
|
* @throws {Error} If authentication fails (401), credits are insufficient (402), or network/proxy errors occur.
|
|
64
100
|
*/
|
|
65
|
-
generateFunctionCallViaProxy(idToken: string, modelName: string, contents: Content[], tools?: Tool[], toolConfig?: ToolConfig, systemInstruction?: string | Content, generationConfig?: any, streamCallbacks?: {
|
|
101
|
+
generateFunctionCallViaProxy(idToken: string, modelName: string, contents: Content[], tools?: Tool[], toolConfig?: ToolConfig, systemInstruction?: string | Content, generationConfig?: ProxyGenerationConfig | any, streamCallbacks?: {
|
|
66
102
|
onChunk: (chunk: string) => void;
|
|
67
103
|
}, abortSignal?: AbortSignal): Promise<{
|
|
68
104
|
functionCall: FunctionCall | null;
|
|
@@ -76,7 +112,7 @@ export declare class ProxyClient {
|
|
|
76
112
|
* Generates text, thoughts, or function calls directly via Google's official Gemini REST API (BYOK mode).
|
|
77
113
|
* Ensures identical return structure and standard role mapping parity with proxy mode.
|
|
78
114
|
*/
|
|
79
|
-
generateViaBYOK(apiKey: string, modelName: string, contents: Content[], tools?: Tool[], toolConfig?: ToolConfig, systemInstruction?: string | Content, generationConfig?: any, streamCallbacks?: {
|
|
115
|
+
generateViaBYOK(apiKey: string, modelName: string, contents: Content[], tools?: Tool[], toolConfig?: ToolConfig, systemInstruction?: string | Content, generationConfig?: ProxyGenerationConfig | any, streamCallbacks?: {
|
|
80
116
|
onChunk: (chunk: string) => void;
|
|
81
117
|
}, abortSignal?: AbortSignal): Promise<{
|
|
82
118
|
functionCall: FunctionCall | null;
|