minovative-mind-cli 2.13.4 → 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 +104 -42
- package/dist/commands/chat.d.ts +1 -1
- package/dist/commands/chat.js +1 -2
- package/dist/services/agent/slashCommands.js +47 -23
- package/dist/services/agent/syntaxAgent.js +13 -0
- package/dist/services/agent/types.d.ts +0 -4
- package/dist/services/agent-tools.d.ts +1 -1
- package/dist/services/agent-tools.js +2 -2
- package/dist/services/agent.d.ts +1 -7
- package/dist/services/agent.js +184 -209
- package/dist/services/ai.d.ts +19 -4
- package/dist/services/ai.js +97 -12
- package/dist/services/contextAgent.js +33 -8
- 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 +27 -4
- package/dist/services/orchestration/messageBus.js +206 -22
- 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/services/swebench/sweBenchRunnerService.js +1 -1
- 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 -4
- package/dist/utils/systemPrompts.js +5 -34
- package/oclif.manifest.json +2 -2
- 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;
|
|
@@ -101,9 +107,10 @@ export declare function coalesceActivityEntries(entries: ActivityEntry[]): Coale
|
|
|
101
107
|
*/
|
|
102
108
|
export declare class MessageBus {
|
|
103
109
|
/** Maximum semantic signals any single agent can post */
|
|
104
|
-
static readonly MAX_SIGNALS_PER_AGENT =
|
|
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[];
|
|
@@ -19,13 +19,24 @@ 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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
}
|
|
29
40
|
export function coalesceActivityEntries(entries) {
|
|
30
41
|
const result = [];
|
|
31
42
|
for (const entry of entries) {
|
|
@@ -64,9 +75,10 @@ export function coalesceActivityEntries(entries) {
|
|
|
64
75
|
*/
|
|
65
76
|
export class MessageBus {
|
|
66
77
|
/** Maximum semantic signals any single agent can post */
|
|
67
|
-
static MAX_SIGNALS_PER_AGENT =
|
|
78
|
+
static MAX_SIGNALS_PER_AGENT = 1000;
|
|
68
79
|
activityCursors = new Map();
|
|
69
80
|
activityLog = [];
|
|
81
|
+
inBandNoticeCursors = new Map();
|
|
70
82
|
persistPath;
|
|
71
83
|
persistQueue = Promise.resolve();
|
|
72
84
|
signalCursors = new Map();
|
|
@@ -99,27 +111,39 @@ export class MessageBus {
|
|
|
99
111
|
// ─── Layer 2: Semantic Signals ───────────────────────────────────
|
|
100
112
|
/**
|
|
101
113
|
* Formats semantic signals into a readable string for agent context injection.
|
|
114
|
+
* Deduplicates identical signal renderings to eliminate payload bloat.
|
|
102
115
|
*/
|
|
103
116
|
static formatSignals(signals) {
|
|
104
117
|
if (signals.length === 0)
|
|
105
118
|
return '';
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
const lines = [];
|
|
121
|
+
for (const s of signals) {
|
|
108
122
|
const tag = s.type.toUpperCase();
|
|
123
|
+
let line = '';
|
|
109
124
|
switch (s.type) {
|
|
110
125
|
case 'discovery':
|
|
111
|
-
|
|
126
|
+
line = ` [${tag} from ${s.fromAgent}]: "${s.content}" (affects: ${s.affectedFiles.join(', ')})`;
|
|
127
|
+
break;
|
|
112
128
|
case 'warning':
|
|
113
|
-
|
|
129
|
+
line = ` [${tag} from ${s.fromAgent}]: "${s.content}"${s.changedSignature ? ` (signature: ${s.changedSignature})` : ''}`;
|
|
130
|
+
break;
|
|
114
131
|
case 'request':
|
|
115
|
-
|
|
132
|
+
line = ` [${tag} from ${s.fromAgent} → ${s.toAgent}]: "${s.content}"`;
|
|
133
|
+
break;
|
|
116
134
|
case 'completion':
|
|
117
|
-
|
|
135
|
+
line = ` [${tag} from ${s.fromAgent}]: "${s.summary}"`;
|
|
136
|
+
break;
|
|
118
137
|
default:
|
|
119
|
-
|
|
138
|
+
line = ` [SIGNAL from ${s.fromAgent}]: ${JSON.stringify(s)}`;
|
|
139
|
+
break;
|
|
120
140
|
}
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
if (!seen.has(line)) {
|
|
142
|
+
seen.add(line);
|
|
143
|
+
lines.push(line);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
123
147
|
}
|
|
124
148
|
// ─── Reading ─────────────────────────────────────────────────────
|
|
125
149
|
/**
|
|
@@ -131,6 +155,7 @@ export class MessageBus {
|
|
|
131
155
|
this.signals = [];
|
|
132
156
|
this.activityCursors = new Map();
|
|
133
157
|
this.signalCursors = new Map();
|
|
158
|
+
this.inBandNoticeCursors = new Map();
|
|
134
159
|
try {
|
|
135
160
|
await fs.unlink(this.persistPath);
|
|
136
161
|
}
|
|
@@ -179,21 +204,76 @@ export class MessageBus {
|
|
|
179
204
|
/**
|
|
180
205
|
* Records a tool execution into the activity log. Called by the scoped tool
|
|
181
206
|
* wrapper in `scopedTools.ts` — zero cost to the agent.
|
|
207
|
+
* Sanitizes and caps result summaries and target paths to eliminate payload bloat.
|
|
182
208
|
*/
|
|
183
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
|
+
}
|
|
184
216
|
this.activityLog.push(entry);
|
|
185
217
|
this.persistToDiskAsync();
|
|
186
218
|
}
|
|
187
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
|
+
}
|
|
188
267
|
/**
|
|
189
268
|
* Peeks urgent signals (breaking warnings or direct requests) that are relevant
|
|
190
269
|
* to a sub-agent's task scope without advancing its cursor. Used for in-band notice
|
|
191
|
-
* delivery
|
|
270
|
+
* delivery or diagnostics.
|
|
192
271
|
*/
|
|
193
272
|
peekUrgentSignals(agentId, scope) {
|
|
194
273
|
const sigCursor = this.signalCursors.get(agentId) ?? 0;
|
|
195
274
|
const unreadSignals = this.signals.slice(sigCursor).filter((s) => s.fromAgent !== agentId);
|
|
196
275
|
const urgent = [];
|
|
276
|
+
const scopedFiles = [...(scope?.targetFiles || []), ...(scope?.readOnlyFiles || [])];
|
|
197
277
|
for (const signal of unreadSignals) {
|
|
198
278
|
// 1. Direct targeted request to this agent
|
|
199
279
|
if (signal.type === 'request' && (signal.toAgent === agentId || signal.toAgent === 'all')) {
|
|
@@ -202,10 +282,22 @@ export class MessageBus {
|
|
|
202
282
|
}
|
|
203
283
|
// 2. Warnings from upstream dependencies or affecting target files
|
|
204
284
|
if (signal.type === 'warning') {
|
|
205
|
-
if (!scope || !scope.dependsOn || scope.dependsOn.includes(signal.fromAgent)) {
|
|
285
|
+
if (!scope || !scope.dependsOn || scope.dependsOn.length === 0 || scope.dependsOn.includes(signal.fromAgent)) {
|
|
206
286
|
urgent.push(signal);
|
|
207
287
|
continue;
|
|
208
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
|
+
}
|
|
209
301
|
}
|
|
210
302
|
// 3. Upstream completion signals from direct dependencies
|
|
211
303
|
if (signal.type === 'completion' && scope?.dependsOn && scope.dependsOn.includes(signal.fromAgent)) {
|
|
@@ -219,11 +311,53 @@ export class MessageBus {
|
|
|
219
311
|
* Posts a semantic signal from an agent. These cost tokens (the agent calls
|
|
220
312
|
* `post_message`) but carry intent that raw tool logs cannot express.
|
|
221
313
|
*
|
|
222
|
-
* Enforces
|
|
314
|
+
* Enforces signal deduplication, content sanitization, and per-agent caps
|
|
315
|
+
* to eliminate redundant message propagation and bus flooding.
|
|
223
316
|
*
|
|
224
317
|
* @returns `true` if the signal was accepted, `false` if the agent hit the cap.
|
|
225
318
|
*/
|
|
226
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
|
+
}
|
|
227
361
|
const agentSignalCount = this.signals.filter((s) => s.fromAgent === signal.fromAgent).length;
|
|
228
362
|
if (agentSignalCount >= MessageBus.MAX_SIGNALS_PER_AGENT) {
|
|
229
363
|
debugLog(`MessageBus: Agent ${signal.fromAgent} hit signal cap (${MessageBus.MAX_SIGNALS_PER_AGENT}). ` +
|
|
@@ -238,13 +372,16 @@ export class MessageBus {
|
|
|
238
372
|
/**
|
|
239
373
|
* Performs an intelligent, scoped query against unread bus activity and signals.
|
|
240
374
|
* Filters out read-only noise for peer sub-agents, enforces unicast delivery for targeted
|
|
241
|
-
* 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.
|
|
242
377
|
*/
|
|
243
378
|
queryBus(options) {
|
|
244
379
|
const actCursor = this.activityCursors.get(options.agentId) ?? 0;
|
|
245
380
|
const sigCursor = this.signalCursors.get(options.agentId) ?? 0;
|
|
246
381
|
const onlyMutations = options.onlyMutations ?? true;
|
|
247
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));
|
|
248
385
|
const activities = this.activityLog.slice(actCursor).filter((e) => {
|
|
249
386
|
// Exclude own actions
|
|
250
387
|
if (e.agentId === options.agentId)
|
|
@@ -252,13 +389,21 @@ export class MessageBus {
|
|
|
252
389
|
// Filter read-only tools if onlyMutations is enabled
|
|
253
390
|
if (onlyMutations && !MUTATING_TOOLS.has(e.tool))
|
|
254
391
|
return false;
|
|
255
|
-
// Optional file filter
|
|
392
|
+
// Optional explicit file filter
|
|
256
393
|
if (normalizedFile) {
|
|
257
394
|
const entryTarget = e.target.replace(/\\/g, '/').toLowerCase();
|
|
258
395
|
if (!entryTarget.includes(normalizedFile) && !normalizedFile.includes(entryTarget)) {
|
|
259
396
|
return false;
|
|
260
397
|
}
|
|
261
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
|
+
}
|
|
262
407
|
// Optional fromAgent filter
|
|
263
408
|
if (options.fromAgent && e.agentId !== options.fromAgent)
|
|
264
409
|
return false;
|
|
@@ -312,6 +457,45 @@ export class MessageBus {
|
|
|
312
457
|
return false;
|
|
313
458
|
}
|
|
314
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
|
+
}
|
|
315
499
|
return true;
|
|
316
500
|
});
|
|
317
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;
|