neoagent 3.2.1-beta.9 → 3.3.1-beta.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/LICENSE +67 -80
- package/README.md +7 -1
- package/docs/licensing.md +44 -0
- package/flutter_app/lib/main.dart +1 -0
- package/flutter_app/lib/main_app_shell.dart +186 -40
- package/flutter_app/lib/main_chat.dart +542 -80
- package/flutter_app/lib/main_controller.dart +66 -5
- package/flutter_app/lib/main_install.dart +9 -7
- package/flutter_app/lib/main_models.dart +171 -22
- package/flutter_app/lib/main_operations.dart +0 -49
- package/flutter_app/lib/main_settings.dart +338 -0
- package/flutter_app/lib/src/backend_client.dart +19 -0
- package/flutter_app/lib/src/local_runtime_paths.dart +60 -0
- package/lib/manager.js +10 -2
- package/package.json +1 -1
- package/runtime/paths.js +70 -0
- package/server/db/database.js +2 -2
- package/server/http/routes.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +84940 -83737
- package/server/routes/behavior.js +80 -0
- package/server/routes/settings.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/history.js +1 -1
- package/server/services/ai/loop/agent_engine_core.js +110 -17
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/completion_judge.js +226 -33
- package/server/services/ai/loop/conversation_loop.js +92 -34
- package/server/services/ai/loop/messaging_delivery.js +47 -0
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loopPolicy.js +24 -2
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_failure_cache.js +7 -0
- package/server/services/ai/systemPrompt.js +31 -122
- package/server/services/ai/taskAnalysis.js +86 -12
- package/server/services/ai/toolEvidence.js +68 -224
- package/server/services/ai/tools.js +60 -19
- package/server/services/behavior/config.js +251 -0
- package/server/services/behavior/defaults.js +68 -0
- package/server/services/behavior/delivery.js +176 -0
- package/server/services/behavior/index.js +43 -0
- package/server/services/behavior/model_client.js +35 -0
- package/server/services/behavior/modules/agent_identity.js +37 -0
- package/server/services/behavior/modules/channel_style.js +28 -0
- package/server/services/behavior/modules/index.js +29 -0
- package/server/services/behavior/modules/norms.js +101 -0
- package/server/services/behavior/modules/persona.js +172 -0
- package/server/services/behavior/modules/persona_prompt.js +238 -0
- package/server/services/behavior/modules/social_memory.js +86 -0
- package/server/services/behavior/modules/social_observability.js +94 -0
- package/server/services/behavior/modules/social_signals.js +41 -0
- package/server/services/behavior/modules/theory_of_mind.js +118 -0
- package/server/services/behavior/modules/turn_taking.js +238 -0
- package/server/services/behavior/pipeline.js +341 -0
- package/server/services/behavior/registry.js +78 -0
- package/server/services/behavior/signals.js +107 -0
- package/server/services/behavior/state.js +99 -0
- package/server/services/behavior/system_prompt.js +75 -0
- package/server/services/memory/manager.js +14 -33
- package/server/services/messaging/access_policy.js +269 -74
- package/server/services/messaging/automation.js +158 -27
- package/server/services/messaging/discord.js +11 -1
- package/server/services/messaging/formatting_guides.js +5 -4
- package/server/services/messaging/http_platforms.js +73 -28
- package/server/services/messaging/inbound_queue.js +74 -13
- package/server/services/messaging/inbound_store.js +33 -0
- package/server/services/messaging/manager.js +57 -5
- package/server/services/messaging/telegram.js +10 -1
- package/server/services/messaging/whatsapp.js +11 -0
- package/server/services/social_reach/channels/social_video.js +1 -1
- package/server/services/social_video/captions.js +2 -2
- package/server/services/social_video/service.js +194 -29
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/runtime.js +2 -2
- package/server/utils/logger.js +19 -0
- package/server/services/ai/terminal_reply.js +0 -57
|
@@ -4,6 +4,7 @@ const COMPLEXITY_LEVELS = ['simple', 'standard', 'complex'];
|
|
|
4
4
|
const AUTONOMY_LEVELS = ['minimal', 'normal', 'high'];
|
|
5
5
|
const PROGRESS_UPDATE_POLICIES = ['none', 'optional', 'required'];
|
|
6
6
|
const COMPLETION_CONFIDENCE_LEVELS = ['medium', 'high'];
|
|
7
|
+
const DRAFT_STATUSES = ['final', 'needs_execution', 'needs_user_input'];
|
|
7
8
|
const TASK_ANALYSIS_SUGGESTED_TOOLS_LIMIT = 12;
|
|
8
9
|
const TASK_ANALYSIS_SUCCESS_CRITERIA_LIMIT = 8;
|
|
9
10
|
const PLAN_STEP_SUGGESTED_TOOLS_LIMIT = 8;
|
|
@@ -17,14 +18,15 @@ const TASK_ANALYSIS_CONFIDENCE_DEFAULT = 0.55;
|
|
|
17
18
|
const VERIFICATION_CONFIDENCE_VERIFIED = 0.85;
|
|
18
19
|
const VERIFICATION_CONFIDENCE_DEFAULT = 0.5;
|
|
19
20
|
const JSON_ONLY_RESPONSE_RULE = 'Return JSON only. No markdown, no prose, no code fences.';
|
|
20
|
-
const { isDeferredWorkReply } = require('./terminal_reply');
|
|
21
21
|
const ANALYSIS_SCHEMA_EXAMPLE = {
|
|
22
22
|
mode: 'execute',
|
|
23
23
|
needs_verification: true,
|
|
24
24
|
draft_reply: '',
|
|
25
|
+
draft_status: 'needs_execution',
|
|
25
26
|
goal: 'Answer the user accurately.',
|
|
26
27
|
success_criteria: ['Final reply is correct and specific.'],
|
|
27
28
|
research_targets: [],
|
|
29
|
+
research_depth: 'none',
|
|
28
30
|
suggested_tools: ['web_search', 'browser_navigate'],
|
|
29
31
|
complexity: 'standard',
|
|
30
32
|
autonomy_level: 'normal',
|
|
@@ -45,11 +47,13 @@ const PLAN_SCHEMA_EXAMPLE = {
|
|
|
45
47
|
verification_focus: ['Confirm the most time-sensitive claim before replying.'],
|
|
46
48
|
};
|
|
47
49
|
const ANALYSIS_PROMPT_INSTRUCTIONS = [
|
|
48
|
-
'Choose the lightest routing mode that still handles the task well.',
|
|
49
|
-
'Use mode="direct_answer" only when a final user-facing reply can be given immediately without tool work.',
|
|
50
|
-
'
|
|
50
|
+
'Choose the lightest routing mode that still handles the task well. Prefer speed for simple work and depth only when the request needs tools or multi-step evidence.',
|
|
51
|
+
'Use mode="direct_answer" only when a final user-facing reply can be given immediately without tool work. The draft_reply must be the actual answer, not a promise to check later.',
|
|
52
|
+
'Set draft_status="final" only when draft_reply fully answers the request now. Use "needs_execution" when any autonomous work remains, and "needs_user_input" only when the draft asks for information that is genuinely required before work can continue.',
|
|
53
|
+
'For short immediate questions, greetings, small explanations, quick conversational replies, or pure opinion/advice that does not need live lookup, prefer mode="direct_answer" with progress_update_policy="none", complexity="simple", autonomy_level="minimal", and an empty research_targets list. Speed matters more than creating plans or tasks.',
|
|
51
54
|
'Use mode="execute" for normal tool-driven work without a separate planning step.',
|
|
52
55
|
'Use mode="plan_execute" only when the task is genuinely multi-step, broad, or coordination-heavy.',
|
|
56
|
+
'Never invent entities, products, people, files, or outcomes to fill gaps. If the user refers to multiple targets but only one is known, ask once or research only the known ones and mark the rest unknown.',
|
|
53
57
|
'Set needs_verification=true when the final answer should be checked against tool evidence before it is sent.',
|
|
54
58
|
'Set goal to a concise restatement of what the user is asking for in this message. Never leave goal empty.',
|
|
55
59
|
'Keep goal and success_criteria short and practical.',
|
|
@@ -61,6 +65,7 @@ const ANALYSIS_PROMPT_INSTRUCTIONS = [
|
|
|
61
65
|
'Set parallel_work=true when independent tool calls or subagents can materially reduce latency.',
|
|
62
66
|
'Set completion_confidence_required="high" when wrong completion would be costly, state-changing, user-visible, or hard to recover.',
|
|
63
67
|
'For research, product/device comparisons, reviews, fact-checks, or multi-entity look-into requests, use mode="execute" or mode="plan_execute", never direct_answer. Set needs_verification=true, freshness_risk="possible" or higher, and completion_confidence_required="high".',
|
|
68
|
+
'Set research_depth="none" when external source research is not needed, "light" for a focused lookup, and "deep" for comparisons, multi-source synthesis, disputed claims, or broad investigation. This is the authoritative research-routing field; do not infer it from tool names.',
|
|
64
69
|
'When the user asks to look into multiple devices, products, options, or entities, put each named target into research_targets and success_criteria and keep them exact. Prefer suggested_tools that can open primary sources (web_search plus browser_navigate/http_request) rather than answering from memory.',
|
|
65
70
|
];
|
|
66
71
|
const PLAN_PROMPT_INSTRUCTIONS = [
|
|
@@ -87,7 +92,9 @@ const VERIFIER_PROMPT_INSTRUCTIONS = [
|
|
|
87
92
|
'If external evidence conflicts with memory, history, or another tool result, preserve the uncertainty instead of flattening it into a single confident claim.',
|
|
88
93
|
'When the draft reply is already correct and fully supported by the evidence, return it unchanged. Do not rewrite for style.',
|
|
89
94
|
'For multi-entity research or comparison replies, every compared target needs supporting tool evidence. If a target was never searched or opened, mark insufficient_evidence and rewrite the reply so it does not invent that target\'s specs.',
|
|
95
|
+
'Do not invent missing targets, products, people, or outcomes. If the draft invents an entity that is not in the task and not in tool evidence, rewrite it out or mark insufficient_evidence.',
|
|
90
96
|
'Search-result snippets alone are weak evidence for concrete product claims. Prefer opened pages, fetched docs, or other primary tool output. If only snippets exist, either keep the claim clearly provisional or mark missing_evidence.',
|
|
97
|
+
'Set safe_to_deliver=true only when final_reply is fully supported or has been rewritten into a truthful, clearly limited partial answer with every unsupported claim removed. Otherwise set it false.',
|
|
91
98
|
];
|
|
92
99
|
const EXECUTION_GUIDANCE_ACTION_LINES = [
|
|
93
100
|
'Act end-to-end. Run independent searches or inspections in parallel when possible. Prefer native integration tools and structured APIs over browser automation or shell scraping. Use exact IDs and required parameters; list or search first when you do not have them.',
|
|
@@ -111,6 +118,7 @@ function buildVerifierSchemaExample(finalReply) {
|
|
|
111
118
|
evidence_sources: ['web_search'],
|
|
112
119
|
missing_evidence: [],
|
|
113
120
|
final_reply: finalReply || '',
|
|
121
|
+
safe_to_deliver: true,
|
|
114
122
|
confidence: 0.83,
|
|
115
123
|
};
|
|
116
124
|
}
|
|
@@ -256,6 +264,10 @@ function promoteAnalysisMode(initialMode, { verificationNeed, freshnessRisk, dra
|
|
|
256
264
|
function isDirectAnswerEligibleAnalysis(analysis) {
|
|
257
265
|
if (!analysis || typeof analysis !== 'object') return false;
|
|
258
266
|
const draftReply = String(analysis.draft_reply || '').trim();
|
|
267
|
+
const draftStatus = String(analysis.draft_status || '').trim().toLowerCase();
|
|
268
|
+
const researchTargets = Array.isArray(analysis.research_targets)
|
|
269
|
+
? analysis.research_targets.filter(Boolean)
|
|
270
|
+
: [];
|
|
259
271
|
const promotedMode = promoteAnalysisMode(analysis.mode, {
|
|
260
272
|
verificationNeed: analysis.verification_need,
|
|
261
273
|
freshnessRisk: analysis.freshness_risk,
|
|
@@ -263,10 +275,16 @@ function isDirectAnswerEligibleAnalysis(analysis) {
|
|
|
263
275
|
planningDepth: analysis.planning_depth,
|
|
264
276
|
});
|
|
265
277
|
|
|
278
|
+
// Direct answers are for zero-tool replies only. Any research targets or
|
|
279
|
+
// freshness/verification burden must enter the tool loop.
|
|
266
280
|
return promotedMode === 'direct_answer'
|
|
267
281
|
&& !analysis.needs_subagents
|
|
268
282
|
&& Boolean(draftReply)
|
|
269
|
-
&&
|
|
283
|
+
&& (draftStatus === 'final' || draftStatus === 'needs_user_input')
|
|
284
|
+
&& researchTargets.length === 0
|
|
285
|
+
&& String(analysis.research_depth || 'none') === 'none'
|
|
286
|
+
&& String(analysis.verification_need || 'none') === 'none'
|
|
287
|
+
&& String(analysis.freshness_risk || 'none') === 'none';
|
|
270
288
|
}
|
|
271
289
|
|
|
272
290
|
function extractJsonCandidate(text) {
|
|
@@ -330,8 +348,18 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
330
348
|
'researchTargets',
|
|
331
349
|
TASK_ANALYSIS_SUCCESS_CRITERIA_LIMIT,
|
|
332
350
|
);
|
|
351
|
+
const declaredResearchDepth = pickEnum(
|
|
352
|
+
resolveAliasedValue(raw, fallback, 'research_depth', 'researchDepth', ''),
|
|
353
|
+
['none', 'light', 'deep'],
|
|
354
|
+
'',
|
|
355
|
+
);
|
|
333
356
|
|
|
334
357
|
const draftReply = resolveAliasedText(raw, fallback, 'draft_reply', 'draftReply', '');
|
|
358
|
+
const draftStatus = pickEnum(
|
|
359
|
+
resolveAliasedValue(raw, fallback, 'draft_status', 'draftStatus', ''),
|
|
360
|
+
DRAFT_STATUSES,
|
|
361
|
+
'needs_execution',
|
|
362
|
+
);
|
|
335
363
|
const initialMode = pickEnum(
|
|
336
364
|
raw.mode || fallback.mode,
|
|
337
365
|
ANALYSIS_MODES,
|
|
@@ -377,13 +405,52 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
377
405
|
['none', 'possible', 'high'],
|
|
378
406
|
freshnessRiskFor({ verificationNeed })
|
|
379
407
|
);
|
|
408
|
+
let researchDepth = declaredResearchDepth;
|
|
409
|
+
if (researchTargets.length >= 2) {
|
|
410
|
+
researchDepth = 'deep';
|
|
411
|
+
} else if (researchTargets.length === 1 && researchDepth === 'none') {
|
|
412
|
+
researchDepth = 'light';
|
|
413
|
+
} else if (!researchDepth) {
|
|
414
|
+
if (freshnessRisk === 'high') {
|
|
415
|
+
researchDepth = 'deep';
|
|
416
|
+
} else if (freshnessRisk === 'possible') {
|
|
417
|
+
researchDepth = 'light';
|
|
418
|
+
} else {
|
|
419
|
+
researchDepth = 'none';
|
|
420
|
+
}
|
|
421
|
+
}
|
|
380
422
|
|
|
381
|
-
|
|
423
|
+
let mode = promoteAnalysisMode(initialMode, {
|
|
382
424
|
verificationNeed,
|
|
383
425
|
freshnessRisk,
|
|
384
426
|
draftReply,
|
|
385
427
|
planningDepth,
|
|
386
428
|
});
|
|
429
|
+
if (mode === 'direct_answer' && draftStatus === 'needs_execution') {
|
|
430
|
+
mode = 'execute';
|
|
431
|
+
}
|
|
432
|
+
if (mode === 'direct_answer' && researchDepth !== 'none') {
|
|
433
|
+
mode = 'execute';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Structural promotion: explicit research targets require tool work.
|
|
437
|
+
if (researchTargets.length > 0 && mode === 'direct_answer') {
|
|
438
|
+
mode = planningDepth === 'deep' || researchTargets.length >= 2 ? 'plan_execute' : 'execute';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
let effectiveVerificationNeed = verificationNeed;
|
|
442
|
+
let effectiveFreshnessRisk = freshnessRisk;
|
|
443
|
+
if (researchTargets.length > 0) {
|
|
444
|
+
if (effectiveVerificationNeed === 'none') {
|
|
445
|
+
effectiveVerificationNeed = researchTargets.length >= 2 ? 'required' : 'light';
|
|
446
|
+
}
|
|
447
|
+
if (effectiveFreshnessRisk === 'none') {
|
|
448
|
+
effectiveFreshnessRisk = researchTargets.length >= 2 ? 'possible' : 'possible';
|
|
449
|
+
}
|
|
450
|
+
if (mode === 'execute' && researchTargets.length >= 2) {
|
|
451
|
+
mode = 'plan_execute';
|
|
452
|
+
}
|
|
453
|
+
}
|
|
387
454
|
|
|
388
455
|
const normalizedComplexity = pickEnum(
|
|
389
456
|
resolveAliasedValue(raw, fallback, 'complexity', 'complexity', ''),
|
|
@@ -398,20 +465,22 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
398
465
|
|
|
399
466
|
return {
|
|
400
467
|
mode,
|
|
401
|
-
freshness_risk:
|
|
402
|
-
verification_need:
|
|
403
|
-
planning_depth:
|
|
468
|
+
freshness_risk: effectiveFreshnessRisk,
|
|
469
|
+
verification_need: effectiveVerificationNeed,
|
|
470
|
+
planning_depth: mode === 'plan_execute' ? 'deep' : mode === 'direct_answer' ? 'none' : 'light',
|
|
404
471
|
confidence: clampConfidence(
|
|
405
472
|
raw.confidence ?? fallback.confidence,
|
|
406
473
|
draftReply ? TASK_ANALYSIS_CONFIDENCE_WITH_DRAFT : TASK_ANALYSIS_CONFIDENCE_DEFAULT,
|
|
407
474
|
),
|
|
408
475
|
suggested_tools: suggestedTools,
|
|
409
476
|
needs_subagents: resolveAliasedBoolean(raw, fallback, 'needs_subagents', 'needsSubagents'),
|
|
410
|
-
needs_verification:
|
|
477
|
+
needs_verification: effectiveVerificationNeed !== 'none',
|
|
411
478
|
draft_reply: draftReply,
|
|
479
|
+
draft_status: draftStatus,
|
|
412
480
|
goal: resolveAliasedText(raw, fallback, 'goal', 'goal', ''),
|
|
413
481
|
success_criteria: successCriteria,
|
|
414
482
|
research_targets: researchTargets,
|
|
483
|
+
research_depth: researchDepth,
|
|
415
484
|
complexity: mode === 'plan_execute' ? 'complex' : normalizedComplexity,
|
|
416
485
|
autonomy_level: mode === 'plan_execute' ? 'high' : normalizedAutonomyLevel,
|
|
417
486
|
progress_update_policy: pickEnum(
|
|
@@ -423,7 +492,9 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
423
492
|
completion_confidence_required: pickEnum(
|
|
424
493
|
resolveAliasedValue(raw, fallback, 'completion_confidence_required', 'completionConfidenceRequired', ''),
|
|
425
494
|
COMPLETION_CONFIDENCE_LEVELS,
|
|
426
|
-
|
|
495
|
+
effectiveVerificationNeed === 'required' || mode === 'plan_execute' || researchTargets.length >= 2
|
|
496
|
+
? 'high'
|
|
497
|
+
: 'medium',
|
|
427
498
|
),
|
|
428
499
|
};
|
|
429
500
|
}
|
|
@@ -507,6 +578,8 @@ function normalizeVerificationResult(raw = {}, fallbackReply = '') {
|
|
|
507
578
|
if (status === 'verified' && missingEvidence.length > 0) {
|
|
508
579
|
status = 'insufficient_evidence';
|
|
509
580
|
}
|
|
581
|
+
const safeToDeliver = status === 'verified'
|
|
582
|
+
|| resolveAliasedBoolean(raw, null, 'safe_to_deliver', 'safeToDeliver', false);
|
|
510
583
|
|
|
511
584
|
return {
|
|
512
585
|
status,
|
|
@@ -519,7 +592,8 @@ function normalizeVerificationResult(raw = {}, fallbackReply = '') {
|
|
|
519
592
|
VERIFICATION_EVIDENCE_SOURCES_LIMIT,
|
|
520
593
|
),
|
|
521
594
|
missing_evidence: missingEvidence,
|
|
522
|
-
final_reply: finalReply,
|
|
595
|
+
final_reply: safeToDeliver ? finalReply : '',
|
|
596
|
+
safe_to_deliver: safeToDeliver,
|
|
523
597
|
confidence: clampConfidence(
|
|
524
598
|
raw.confidence,
|
|
525
599
|
status === 'verified' ? VERIFICATION_CONFIDENCE_VERIFIED : VERIFICATION_CONFIDENCE_DEFAULT,
|
|
@@ -200,41 +200,6 @@ 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
203
|
function clampResearchText(value, maxChars = 220) {
|
|
239
204
|
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
240
205
|
if (!text) return '';
|
|
@@ -257,88 +222,29 @@ function uniqueResearchTokens(values = [], { limit = 12 } = {}) {
|
|
|
257
222
|
return tokens;
|
|
258
223
|
}
|
|
259
224
|
|
|
260
|
-
//
|
|
261
|
-
//
|
|
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
|
-
|
|
225
|
+
// Research targets come from structured task analysis / goal contract only.
|
|
226
|
+
// Do not NLP-extract entity names from free text.
|
|
310
227
|
function collectResearchTargets(analysis = null, goalContext = null) {
|
|
311
|
-
|
|
228
|
+
return uniqueResearchTokens([
|
|
312
229
|
...(Array.isArray(analysis?.research_targets) ? analysis.research_targets : []),
|
|
313
230
|
...(Array.isArray(goalContext?.researchTargets) ? goalContext.researchTargets : []),
|
|
314
231
|
], { 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
232
|
}
|
|
340
233
|
|
|
341
234
|
function resolveResearchIntensity(analysis = null, goalContext = null) {
|
|
235
|
+
const declaredDepth = String(
|
|
236
|
+
analysis?.research_depth
|
|
237
|
+
|| analysis?.researchDepth
|
|
238
|
+
|| '',
|
|
239
|
+
).trim().toLowerCase();
|
|
240
|
+
const declaredTargets = collectResearchTargets(analysis, goalContext);
|
|
241
|
+
if (
|
|
242
|
+
['none', 'light', 'deep'].includes(declaredDepth)
|
|
243
|
+
&& !(declaredDepth === 'none' && declaredTargets.length > 0)
|
|
244
|
+
) {
|
|
245
|
+
return declaredDepth;
|
|
246
|
+
}
|
|
247
|
+
|
|
342
248
|
const mode = String(analysis?.mode || '').trim().toLowerCase();
|
|
343
249
|
const complexity = String(
|
|
344
250
|
goalContext?.effectiveComplexity
|
|
@@ -358,21 +264,16 @@ function resolveResearchIntensity(analysis = null, goalContext = null) {
|
|
|
358
264
|
const verificationNeed = String(analysis?.verification_need || '').trim().toLowerCase();
|
|
359
265
|
const freshnessRisk = String(analysis?.freshness_risk || '').trim().toLowerCase();
|
|
360
266
|
const planningDepth = String(analysis?.planning_depth || '').trim().toLowerCase();
|
|
361
|
-
const targets =
|
|
362
|
-
const researchToolHint = hasResearchToolHint(analysis);
|
|
267
|
+
const targets = declaredTargets;
|
|
363
268
|
|
|
364
269
|
if (mode === 'direct_answer' && verificationNeed === 'none' && freshnessRisk === 'none') {
|
|
365
270
|
return 'none';
|
|
366
271
|
}
|
|
367
272
|
|
|
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
273
|
const needsExternalEvidence = (
|
|
371
274
|
targets.length > 0
|
|
372
|
-
|| researchToolHint
|
|
373
275
|
|| freshnessRisk === 'possible'
|
|
374
276
|
|| freshnessRisk === 'high'
|
|
375
|
-
|| verificationNeed === 'required'
|
|
376
277
|
);
|
|
377
278
|
if (!needsExternalEvidence) {
|
|
378
279
|
return 'none';
|
|
@@ -403,32 +304,32 @@ function isSuccessfulResearchExecution(item = {}) {
|
|
|
403
304
|
return Boolean(item.evidenceRelevant || item.dependsOnOutput || item.stateChanged);
|
|
404
305
|
}
|
|
405
306
|
|
|
406
|
-
function
|
|
407
|
-
|
|
307
|
+
function selectResearchEvidenceCandidates(toolExecutions = [], maxItems = 80) {
|
|
308
|
+
const candidates = (Array.isArray(toolExecutions) ? toolExecutions : [])
|
|
309
|
+
.map((execution, index) => ({ execution, evidenceIndex: index + 1 }))
|
|
310
|
+
.filter(({ execution }) => isSuccessfulResearchExecution(execution));
|
|
311
|
+
if (candidates.length <= maxItems) return candidates;
|
|
312
|
+
|
|
313
|
+
const firstCount = Math.floor(maxItems / 4);
|
|
314
|
+
return [
|
|
315
|
+
...candidates.slice(0, firstCount),
|
|
316
|
+
...candidates.slice(-(maxItems - firstCount)),
|
|
317
|
+
];
|
|
408
318
|
}
|
|
409
319
|
|
|
410
|
-
function
|
|
411
|
-
return
|
|
320
|
+
function summarizeResearchEvidenceCatalog(toolExecutions = [], maxItems = 80) {
|
|
321
|
+
return selectResearchEvidenceCandidates(toolExecutions, maxItems)
|
|
322
|
+
.map(({ execution, evidenceIndex }) => (
|
|
323
|
+
`E${evidenceIndex}. ${execution.toolName} [${execution.evidenceSource || 'tool'}] :: ${clampRunContext(execution.summary || '', 160)}`
|
|
324
|
+
))
|
|
325
|
+
.join('\n');
|
|
412
326
|
}
|
|
413
327
|
|
|
414
|
-
function
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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);
|
|
328
|
+
function researchExecutionSignature(execution = {}) {
|
|
329
|
+
return JSON.stringify([
|
|
330
|
+
String(execution.toolName || '').trim(),
|
|
331
|
+
execution.input && typeof execution.input === 'object' ? execution.input : {},
|
|
332
|
+
]);
|
|
432
333
|
}
|
|
433
334
|
|
|
434
335
|
function assessResearchAdequacy({
|
|
@@ -441,111 +342,53 @@ function assessResearchAdequacy({
|
|
|
441
342
|
|
|
442
343
|
const successful = (Array.isArray(toolExecutions) ? toolExecutions : [])
|
|
443
344
|
.filter(isSuccessfulResearchExecution);
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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;
|
|
345
|
+
const uniqueEvidenceCandidateCount = new Set(
|
|
346
|
+
successful.map((item) => researchExecutionSignature(item)),
|
|
347
|
+
).size;
|
|
348
|
+
const structurallyReady = intensity === 'none' || uniqueEvidenceCandidateCount > 0;
|
|
349
|
+
const missing = structurallyReady
|
|
350
|
+
? []
|
|
351
|
+
: ['No successful source-bearing tool evidence is available for semantic review.'];
|
|
497
352
|
const nextActions = [];
|
|
498
|
-
if (!
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
}
|
|
353
|
+
if (!structurallyReady) {
|
|
354
|
+
nextActions.push('Gather source-backed evidence before requesting completion.');
|
|
355
|
+
} else if (intensity !== 'none') {
|
|
356
|
+
nextActions.push('Have the completion judge map each requested target to concrete evidence entries and assess source quality.');
|
|
514
357
|
}
|
|
515
358
|
|
|
516
359
|
return {
|
|
517
360
|
intensity,
|
|
518
|
-
adequate,
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
primarySourceCount: primary.length,
|
|
524
|
-
secondarySourceCount: secondary.length,
|
|
361
|
+
adequate: structurallyReady,
|
|
362
|
+
structurallyReady,
|
|
363
|
+
semanticReviewRequired: intensity !== 'none',
|
|
364
|
+
evidenceCandidateCount: successful.length,
|
|
365
|
+
uniqueEvidenceCandidateCount,
|
|
525
366
|
targets,
|
|
526
|
-
coveredTargets,
|
|
527
|
-
primaryCoveredTargets,
|
|
528
|
-
uncoveredTargets,
|
|
529
|
-
primaryUncoveredTargets,
|
|
367
|
+
coveredTargets: [],
|
|
368
|
+
primaryCoveredTargets: [],
|
|
369
|
+
uncoveredTargets: intensity === 'none' ? [] : targets,
|
|
370
|
+
primaryUncoveredTargets: intensity === 'none' ? [] : targets,
|
|
530
371
|
missing,
|
|
531
372
|
nextActions,
|
|
532
|
-
reason:
|
|
373
|
+
reason: structurallyReady
|
|
533
374
|
? (intensity === 'none'
|
|
534
375
|
? 'No research burden for this run.'
|
|
535
|
-
: '
|
|
376
|
+
: 'Source candidates exist; the AI completion judge must still validate target coverage and source quality.')
|
|
536
377
|
: clampResearchText(missing.join(' ') || 'Research evidence is still incomplete.', 320),
|
|
537
378
|
};
|
|
538
379
|
}
|
|
539
380
|
|
|
540
381
|
function formatResearchAdequacyGuidance(assessment = null) {
|
|
541
|
-
if (!assessment || assessment.
|
|
382
|
+
if (!assessment || assessment.intensity === 'none') return '';
|
|
542
383
|
const lines = [
|
|
543
|
-
|
|
384
|
+
assessment.structurallyReady
|
|
385
|
+
? 'Research self-check: source candidates exist, but semantic target coverage is not inferred from tool names, arguments, or token overlap.'
|
|
386
|
+
: 'Research self-check: no successful source evidence is available yet.',
|
|
544
387
|
assessment.reason ? `Gap: ${assessment.reason}` : '',
|
|
545
388
|
assessment.nextActions?.length
|
|
546
389
|
? `Next safe steps:\n- ${assessment.nextActions.join('\n- ')}`
|
|
547
390
|
: '',
|
|
548
|
-
'
|
|
391
|
+
'The completion judge must explicitly map every requested target to supporting evidence and judge whether that evidence is primary, secondary, or merely contextual.',
|
|
549
392
|
];
|
|
550
393
|
return lines.filter(Boolean).join('\n');
|
|
551
394
|
}
|
|
@@ -625,12 +468,13 @@ module.exports = {
|
|
|
625
468
|
classifyToolExecution,
|
|
626
469
|
deriveEvidenceSource,
|
|
627
470
|
assessResearchAdequacy,
|
|
628
|
-
extractResearchTargets,
|
|
629
471
|
formatResearchAdequacyGuidance,
|
|
630
472
|
gatheredNewEvidence,
|
|
631
473
|
isSubstantiveProgressEvidence,
|
|
632
474
|
isSubstantiveProgressToolName,
|
|
633
475
|
resolveResearchIntensity,
|
|
476
|
+
selectResearchEvidenceCandidates,
|
|
477
|
+
summarizeResearchEvidenceCatalog,
|
|
634
478
|
summarizeProgressToolExecutions,
|
|
635
479
|
summarizeToolExecutions,
|
|
636
480
|
summarizeAvailableTools,
|