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.
@@ -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 in scopedTools.
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 per-agent signal cap to prevent runaway agents from flooding the bus.
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 supports selective querying by file, agent, or signal type.
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
- return signals
101
- .map((s) => {
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
- return ` [${tag} from ${s.fromAgent}]: "${s.content}" (affects: ${s.affectedFiles.join(', ')})`;
126
+ line = ` [${tag} from ${s.fromAgent}]: "${s.content}" (affects: ${s.affectedFiles.join(', ')})`;
127
+ break;
106
128
  case 'warning':
107
- return ` [${tag} from ${s.fromAgent}]: "${s.content}"${s.changedSignature ? ` (signature: ${s.changedSignature})` : ''}`;
129
+ line = ` [${tag} from ${s.fromAgent}]: "${s.content}"${s.changedSignature ? ` (signature: ${s.changedSignature})` : ''}`;
130
+ break;
108
131
  case 'request':
109
- return ` [${tag} from ${s.fromAgent} → ${s.toAgent}]: "${s.content}"`;
132
+ line = ` [${tag} from ${s.fromAgent} → ${s.toAgent}]: "${s.content}"`;
133
+ break;
110
134
  case 'completion':
111
- return ` [${tag} from ${s.fromAgent}]: "${s.summary}"`;
135
+ line = ` [${tag} from ${s.fromAgent}]: "${s.summary}"`;
136
+ break;
112
137
  default:
113
- return ` [SIGNAL from ${s.fromAgent}]: ${JSON.stringify(s)}`;
138
+ line = ` [SIGNAL from ${s.fromAgent}]: ${JSON.stringify(s)}`;
139
+ break;
114
140
  }
115
- })
116
- .join('\n');
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 in scopedTools.
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 per-agent signal cap to prevent runaway agents from flooding the bus.
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 supports selective querying by file, agent, or signal type.
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
- if (typeof result === 'object' && result !== null && typeof bus?.peekUrgentSignals === 'function') {
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?.peekUrgentSignals === 'function') {
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
- // Sub-agents default to flash-3.7 for better reasoning capabilities
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;