neoagent 3.2.1-beta.7 → 3.2.1-beta.9
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/package.json +2 -2
- package/server/http/routes.js +7 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +5 -5
- package/server/services/ai/loop/agent_engine_core.js +21 -1
- package/server/services/ai/loop/completion_judge.js +91 -13
- package/server/services/ai/loop/conversation_loop.js +122 -129
- package/server/services/ai/model_failure_cache.js +47 -8
- package/server/services/ai/providers/google.js +101 -95
- package/server/services/ai/taskAnalysis.js +15 -0
- package/server/services/ai/terminal_reply.js +13 -1
- package/server/services/ai/toolEvidence.js +354 -0
|
@@ -200,6 +200,356 @@ function isSubstantiveProgressToolName(toolName = '') {
|
|
|
200
200
|
return true;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
const PRIMARY_RESEARCH_SOURCES = new Set([
|
|
204
|
+
'browser',
|
|
205
|
+
'http',
|
|
206
|
+
'integration',
|
|
207
|
+
'files',
|
|
208
|
+
'command',
|
|
209
|
+
'mcp',
|
|
210
|
+
'android',
|
|
211
|
+
'data',
|
|
212
|
+
'vision',
|
|
213
|
+
'skills',
|
|
214
|
+
'subagent',
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
const SECONDARY_RESEARCH_SOURCES = new Set([
|
|
218
|
+
'search',
|
|
219
|
+
'memory',
|
|
220
|
+
]);
|
|
221
|
+
|
|
222
|
+
// External/source-backed tools only. Local file/edit/shell work must not
|
|
223
|
+
// inherit a research burden just because inspection tools are available.
|
|
224
|
+
const RESEARCH_TOOL_HINTS = new Set([
|
|
225
|
+
'web_search',
|
|
226
|
+
'http_request',
|
|
227
|
+
'browser_navigate',
|
|
228
|
+
'browser_open',
|
|
229
|
+
'browser_click',
|
|
230
|
+
'browser_type',
|
|
231
|
+
'browser_snapshot',
|
|
232
|
+
'browser_evaluate',
|
|
233
|
+
'analyze_image',
|
|
234
|
+
'spawn_subagent',
|
|
235
|
+
'delegate_to_agent',
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
function clampResearchText(value, maxChars = 220) {
|
|
239
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
240
|
+
if (!text) return '';
|
|
241
|
+
if (text.length <= maxChars) return text;
|
|
242
|
+
return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function uniqueResearchTokens(values = [], { limit = 12 } = {}) {
|
|
246
|
+
const seen = new Set();
|
|
247
|
+
const tokens = [];
|
|
248
|
+
for (const value of values) {
|
|
249
|
+
const token = String(value || '').replace(/\s+/g, ' ').trim();
|
|
250
|
+
if (!token) continue;
|
|
251
|
+
const key = token.toLowerCase();
|
|
252
|
+
if (seen.has(key)) continue;
|
|
253
|
+
seen.add(key);
|
|
254
|
+
tokens.push(token);
|
|
255
|
+
if (tokens.length >= limit) break;
|
|
256
|
+
}
|
|
257
|
+
return tokens;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Structural target extraction only: quoted spans and product-like proper names.
|
|
261
|
+
// No phrase-based intent filters.
|
|
262
|
+
function isProductLikeToken(token = '') {
|
|
263
|
+
const value = String(token || '').trim();
|
|
264
|
+
if (!value) return false;
|
|
265
|
+
if (/[0-9]/.test(value)) return true;
|
|
266
|
+
if (/[-_/]/.test(value)) return true;
|
|
267
|
+
if (/[a-z][A-Z]/.test(value)) return true;
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function extractResearchTargets(text = '') {
|
|
272
|
+
const raw = String(text || '');
|
|
273
|
+
if (!raw.trim()) return [];
|
|
274
|
+
|
|
275
|
+
const targets = [];
|
|
276
|
+
const quoted = raw.match(/["“”'‘’`]([^"“”'‘’`]{2,80})["“”'‘’`]/g) || [];
|
|
277
|
+
for (const match of quoted) {
|
|
278
|
+
const cleaned = match.replace(/^["“”'‘’`]|["“”'‘’`]$/g, '').trim();
|
|
279
|
+
if (cleaned) targets.push(cleaned);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Require each subsequent token to start with a capital letter or digit so
|
|
283
|
+
// ordinary sentence fragments ("Implement the pause...") do not become targets.
|
|
284
|
+
const productish = raw.match(
|
|
285
|
+
/\b[A-Z][A-Za-z0-9+./-]*(?:[ -][A-Z0-9][A-Za-z0-9+./-]*){0,5}\b/g,
|
|
286
|
+
) || [];
|
|
287
|
+
for (const match of productish) {
|
|
288
|
+
const cleaned = match.replace(/\s+/g, ' ').trim();
|
|
289
|
+
const parts = cleaned.split(/\s+/).filter(Boolean);
|
|
290
|
+
if (parts.length === 0) continue;
|
|
291
|
+
if (parts.length === 1) {
|
|
292
|
+
if (!isProductLikeToken(parts[0]) || parts[0].length < 4) continue;
|
|
293
|
+
} else if (!parts.some(isProductLikeToken) && parts.length < 2) {
|
|
294
|
+
continue;
|
|
295
|
+
} else if (!parts.some(isProductLikeToken) && parts.every((part) => part.length <= 3)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
// Drop pure sentence openers with no product-like signal.
|
|
299
|
+
if (parts.length >= 2 && !parts.some(isProductLikeToken)) {
|
|
300
|
+
// Keep multi-token proper names such as brand + model family only when
|
|
301
|
+
// at least one token is long enough to be a meaningful entity name.
|
|
302
|
+
if (!parts.some((part) => part.length >= 5)) continue;
|
|
303
|
+
}
|
|
304
|
+
targets.push(cleaned);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return uniqueResearchTokens(targets, { limit: 8 });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function collectResearchTargets(analysis = null, goalContext = null) {
|
|
311
|
+
const explicit = uniqueResearchTokens([
|
|
312
|
+
...(Array.isArray(analysis?.research_targets) ? analysis.research_targets : []),
|
|
313
|
+
...(Array.isArray(goalContext?.researchTargets) ? goalContext.researchTargets : []),
|
|
314
|
+
], { limit: 8 });
|
|
315
|
+
if (explicit.length > 0) return explicit;
|
|
316
|
+
|
|
317
|
+
const goalText = [
|
|
318
|
+
goalContext?.effectiveGoal,
|
|
319
|
+
analysis?.goal,
|
|
320
|
+
...(Array.isArray(goalContext?.successCriteria) ? goalContext.successCriteria : []),
|
|
321
|
+
...(Array.isArray(analysis?.success_criteria) ? analysis.success_criteria : []),
|
|
322
|
+
].filter(Boolean).join(' ');
|
|
323
|
+
|
|
324
|
+
return extractResearchTargets(goalText);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function hasResearchToolHint(analysis = null) {
|
|
328
|
+
const tools = Array.isArray(analysis?.suggested_tools) ? analysis.suggested_tools : [];
|
|
329
|
+
return tools.some((name) => {
|
|
330
|
+
const tool = String(name || '').trim().toLowerCase();
|
|
331
|
+
if (!tool) return false;
|
|
332
|
+
if (RESEARCH_TOOL_HINTS.has(tool)) return true;
|
|
333
|
+
return tool.startsWith('browser_')
|
|
334
|
+
|| tool.startsWith('mcp_')
|
|
335
|
+
|| tool === 'web_search'
|
|
336
|
+
|| tool.includes('web_search')
|
|
337
|
+
|| tool.includes('http_request');
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function resolveResearchIntensity(analysis = null, goalContext = null) {
|
|
342
|
+
const mode = String(analysis?.mode || '').trim().toLowerCase();
|
|
343
|
+
const complexity = String(
|
|
344
|
+
goalContext?.effectiveComplexity
|
|
345
|
+
|| analysis?.complexity
|
|
346
|
+
|| '',
|
|
347
|
+
).trim().toLowerCase();
|
|
348
|
+
const autonomyLevel = String(
|
|
349
|
+
goalContext?.effectiveAutonomyLevel
|
|
350
|
+
|| analysis?.autonomy_level
|
|
351
|
+
|| '',
|
|
352
|
+
).trim().toLowerCase();
|
|
353
|
+
const completionConfidence = String(
|
|
354
|
+
goalContext?.effectiveCompletionConfidence
|
|
355
|
+
|| analysis?.completion_confidence_required
|
|
356
|
+
|| '',
|
|
357
|
+
).trim().toLowerCase();
|
|
358
|
+
const verificationNeed = String(analysis?.verification_need || '').trim().toLowerCase();
|
|
359
|
+
const freshnessRisk = String(analysis?.freshness_risk || '').trim().toLowerCase();
|
|
360
|
+
const planningDepth = String(analysis?.planning_depth || '').trim().toLowerCase();
|
|
361
|
+
const targets = collectResearchTargets(analysis, goalContext);
|
|
362
|
+
const researchToolHint = hasResearchToolHint(analysis);
|
|
363
|
+
|
|
364
|
+
if (mode === 'direct_answer' && verificationNeed === 'none' && freshnessRisk === 'none') {
|
|
365
|
+
return 'none';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// External/source-backed work only. Pure local implementation tasks must not
|
|
369
|
+
// inherit a research burden just because they are complex or multi-step.
|
|
370
|
+
const needsExternalEvidence = (
|
|
371
|
+
targets.length > 0
|
|
372
|
+
|| researchToolHint
|
|
373
|
+
|| freshnessRisk === 'possible'
|
|
374
|
+
|| freshnessRisk === 'high'
|
|
375
|
+
|| verificationNeed === 'required'
|
|
376
|
+
);
|
|
377
|
+
if (!needsExternalEvidence) {
|
|
378
|
+
return 'none';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const deepSignals = [
|
|
382
|
+
mode === 'plan_execute',
|
|
383
|
+
complexity === 'complex',
|
|
384
|
+
autonomyLevel === 'high',
|
|
385
|
+
completionConfidence === 'high',
|
|
386
|
+
verificationNeed === 'required',
|
|
387
|
+
freshnessRisk === 'high',
|
|
388
|
+
planningDepth === 'deep',
|
|
389
|
+
targets.length >= 2,
|
|
390
|
+
].filter(Boolean).length;
|
|
391
|
+
|
|
392
|
+
if (deepSignals >= 2 || targets.length >= 2 || verificationNeed === 'required' || freshnessRisk === 'high') {
|
|
393
|
+
return 'deep';
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return 'light';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function isSuccessfulResearchExecution(item = {}) {
|
|
400
|
+
if (!item || item.ok !== true) return false;
|
|
401
|
+
if (!isSubstantiveProgressToolName(item.toolName)) return false;
|
|
402
|
+
if (item.evidenceSource === 'messaging') return false;
|
|
403
|
+
return Boolean(item.evidenceRelevant || item.dependsOnOutput || item.stateChanged);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function isPrimaryResearchSource(source = '') {
|
|
407
|
+
return PRIMARY_RESEARCH_SOURCES.has(String(source || '').trim().toLowerCase());
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function isSecondaryResearchSource(source = '') {
|
|
411
|
+
return SECONDARY_RESEARCH_SOURCES.has(String(source || '').trim().toLowerCase());
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function executionMentionsTarget(execution = {}, target = '') {
|
|
415
|
+
const needle = String(target || '').trim().toLowerCase();
|
|
416
|
+
if (!needle) return false;
|
|
417
|
+
const haystack = [
|
|
418
|
+
execution.summary,
|
|
419
|
+
execution.toolName,
|
|
420
|
+
execution.evidenceSource,
|
|
421
|
+
JSON.stringify(execution.input || {}),
|
|
422
|
+
].join(' ').toLowerCase();
|
|
423
|
+
if (haystack.includes(needle)) return true;
|
|
424
|
+
|
|
425
|
+
const tokens = needle
|
|
426
|
+
.split(/[^a-z0-9+]+/i)
|
|
427
|
+
.map((token) => token.trim())
|
|
428
|
+
.filter((token) => token.length >= 3);
|
|
429
|
+
if (tokens.length === 0) return false;
|
|
430
|
+
const matched = tokens.filter((token) => haystack.includes(token)).length;
|
|
431
|
+
return matched >= Math.min(2, tokens.length);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function assessResearchAdequacy({
|
|
435
|
+
analysis = null,
|
|
436
|
+
goalContext = null,
|
|
437
|
+
toolExecutions = [],
|
|
438
|
+
} = {}) {
|
|
439
|
+
const intensity = resolveResearchIntensity(analysis, goalContext);
|
|
440
|
+
const targets = collectResearchTargets(analysis, goalContext);
|
|
441
|
+
|
|
442
|
+
const successful = (Array.isArray(toolExecutions) ? toolExecutions : [])
|
|
443
|
+
.filter(isSuccessfulResearchExecution);
|
|
444
|
+
const primary = successful.filter((item) => isPrimaryResearchSource(item.evidenceSource));
|
|
445
|
+
const secondary = successful.filter((item) => isSecondaryResearchSource(item.evidenceSource));
|
|
446
|
+
const coveredTargets = targets.filter((target) => (
|
|
447
|
+
successful.some((item) => executionMentionsTarget(item, target))
|
|
448
|
+
));
|
|
449
|
+
const primaryCoveredTargets = targets.filter((target) => (
|
|
450
|
+
primary.some((item) => executionMentionsTarget(item, target))
|
|
451
|
+
));
|
|
452
|
+
const uncoveredTargets = targets.filter((target) => !coveredTargets.includes(target));
|
|
453
|
+
const primaryUncoveredTargets = targets.filter((target) => !primaryCoveredTargets.includes(target));
|
|
454
|
+
|
|
455
|
+
const requiredPrimarySources = intensity === 'deep'
|
|
456
|
+
? (targets.length > 0 ? Math.min(4, targets.length) : 2)
|
|
457
|
+
: intensity === 'light'
|
|
458
|
+
? 1
|
|
459
|
+
: 0;
|
|
460
|
+
const requiredSecondarySources = intensity === 'deep' ? 1 : 0;
|
|
461
|
+
const requiredTargetCoverage = intensity === 'deep'
|
|
462
|
+
? targets.length
|
|
463
|
+
: intensity === 'light'
|
|
464
|
+
? Math.min(targets.length, 1)
|
|
465
|
+
: 0;
|
|
466
|
+
const requiredPrimaryTargetCoverage = intensity === 'deep'
|
|
467
|
+
? targets.length
|
|
468
|
+
: 0;
|
|
469
|
+
|
|
470
|
+
const missing = [];
|
|
471
|
+
if (primary.length < requiredPrimarySources) {
|
|
472
|
+
missing.push(
|
|
473
|
+
`Need ${requiredPrimarySources} primary source open/fetch/inspect step(s); have ${primary.length}.`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
if (secondary.length < requiredSecondarySources && primary.length < requiredPrimarySources) {
|
|
477
|
+
missing.push('Need at least one search lead before finishing deep research.');
|
|
478
|
+
}
|
|
479
|
+
if (targets.length > 0 && coveredTargets.length < requiredTargetCoverage) {
|
|
480
|
+
missing.push(
|
|
481
|
+
`Need evidence for: ${uncoveredTargets.slice(0, 4).join('; ') || targets.slice(0, 4).join('; ')}.`,
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
if (targets.length > 0 && primaryCoveredTargets.length < requiredPrimaryTargetCoverage) {
|
|
485
|
+
missing.push(
|
|
486
|
+
`Need primary-source evidence for: ${primaryUncoveredTargets.slice(0, 4).join('; ') || targets.slice(0, 4).join('; ')}.`,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (intensity === 'deep' && primary.length === 0 && secondary.length > 0) {
|
|
490
|
+
missing.push('Search snippets alone are not enough; open primary sources for the key claims.');
|
|
491
|
+
}
|
|
492
|
+
if (intensity === 'light' && primary.length === 0 && secondary.length === 0) {
|
|
493
|
+
missing.push('Need at least one successful search or primary-source check before finishing.');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const adequate = intensity === 'none' ? true : missing.length === 0;
|
|
497
|
+
const nextActions = [];
|
|
498
|
+
if (!adequate) {
|
|
499
|
+
if (primaryUncoveredTargets.length > 0) {
|
|
500
|
+
nextActions.push(
|
|
501
|
+
`Open or fetch primary sources for each remaining target: ${primaryUncoveredTargets.slice(0, 4).join('; ')}.`,
|
|
502
|
+
);
|
|
503
|
+
} else if (uncoveredTargets.length > 0) {
|
|
504
|
+
nextActions.push(
|
|
505
|
+
`Research each remaining target separately: ${uncoveredTargets.slice(0, 4).join('; ')}.`,
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
if (primary.length < requiredPrimarySources) {
|
|
509
|
+
nextActions.push('Open or fetch primary pages/docs for the remaining claims instead of guessing from memory or snippets.');
|
|
510
|
+
}
|
|
511
|
+
if (secondary.length === 0 && intensity === 'deep') {
|
|
512
|
+
nextActions.push('Run targeted searches, then open the strongest sources.');
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return {
|
|
517
|
+
intensity,
|
|
518
|
+
adequate,
|
|
519
|
+
requiredPrimarySources,
|
|
520
|
+
requiredSecondarySources,
|
|
521
|
+
requiredTargetCoverage,
|
|
522
|
+
requiredPrimaryTargetCoverage,
|
|
523
|
+
primarySourceCount: primary.length,
|
|
524
|
+
secondarySourceCount: secondary.length,
|
|
525
|
+
targets,
|
|
526
|
+
coveredTargets,
|
|
527
|
+
primaryCoveredTargets,
|
|
528
|
+
uncoveredTargets,
|
|
529
|
+
primaryUncoveredTargets,
|
|
530
|
+
missing,
|
|
531
|
+
nextActions,
|
|
532
|
+
reason: adequate
|
|
533
|
+
? (intensity === 'none'
|
|
534
|
+
? 'No research burden for this run.'
|
|
535
|
+
: 'Research evidence covers the requested targets.')
|
|
536
|
+
: clampResearchText(missing.join(' ') || 'Research evidence is still incomplete.', 320),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function formatResearchAdequacyGuidance(assessment = null) {
|
|
541
|
+
if (!assessment || assessment.adequate !== false) return '';
|
|
542
|
+
const lines = [
|
|
543
|
+
'Research self-check: evidence is still incomplete for this run.',
|
|
544
|
+
assessment.reason ? `Gap: ${assessment.reason}` : '',
|
|
545
|
+
assessment.nextActions?.length
|
|
546
|
+
? `Next safe steps:\n- ${assessment.nextActions.join('\n- ')}`
|
|
547
|
+
: '',
|
|
548
|
+
'Do not complete with memory, guesses, or a partial comparison while these gaps remain. Gather the missing evidence first, or return a blocker that names exactly what could not be verified.',
|
|
549
|
+
];
|
|
550
|
+
return lines.filter(Boolean).join('\n');
|
|
551
|
+
}
|
|
552
|
+
|
|
203
553
|
function isSubstantiveProgressEvidence(item = {}) {
|
|
204
554
|
if (!isSubstantiveProgressToolName(item.toolName)) return false;
|
|
205
555
|
if (item.evidenceSource === 'messaging') return false;
|
|
@@ -274,9 +624,13 @@ function buildAutonomousRecoveryContext({ err, toolExecutions = [], tools = [],
|
|
|
274
624
|
module.exports = {
|
|
275
625
|
classifyToolExecution,
|
|
276
626
|
deriveEvidenceSource,
|
|
627
|
+
assessResearchAdequacy,
|
|
628
|
+
extractResearchTargets,
|
|
629
|
+
formatResearchAdequacyGuidance,
|
|
277
630
|
gatheredNewEvidence,
|
|
278
631
|
isSubstantiveProgressEvidence,
|
|
279
632
|
isSubstantiveProgressToolName,
|
|
633
|
+
resolveResearchIntensity,
|
|
280
634
|
summarizeProgressToolExecutions,
|
|
281
635
|
summarizeToolExecutions,
|
|
282
636
|
summarizeAvailableTools,
|