neoagent 3.2.1-beta.8 → 3.3.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/flutter_app/lib/main_app_shell.dart +178 -34
- package/flutter_app/lib/main_chat.dart +542 -80
- package/flutter_app/lib/main_controller.dart +59 -4
- 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/package.json +1 -1
- 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 +85079 -83904
- 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 +131 -18
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/completion_judge.js +280 -18
- 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 +101 -12
- package/server/services/ai/toolEvidence.js +198 -0
- 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,13 +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.'],
|
|
28
|
+
research_targets: [],
|
|
29
|
+
research_depth: 'none',
|
|
27
30
|
suggested_tools: ['web_search', 'browser_navigate'],
|
|
28
31
|
complexity: 'standard',
|
|
29
32
|
autonomy_level: 'normal',
|
|
@@ -44,11 +47,13 @@ const PLAN_SCHEMA_EXAMPLE = {
|
|
|
44
47
|
verification_focus: ['Confirm the most time-sensitive claim before replying.'],
|
|
45
48
|
};
|
|
46
49
|
const ANALYSIS_PROMPT_INSTRUCTIONS = [
|
|
47
|
-
'Choose the lightest routing mode that still handles the task well.',
|
|
48
|
-
'Use mode="direct_answer" only when a final user-facing reply can be given immediately without tool work.',
|
|
49
|
-
'
|
|
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.',
|
|
50
54
|
'Use mode="execute" for normal tool-driven work without a separate planning step.',
|
|
51
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.',
|
|
52
57
|
'Set needs_verification=true when the final answer should be checked against tool evidence before it is sent.',
|
|
53
58
|
'Set goal to a concise restatement of what the user is asking for in this message. Never leave goal empty.',
|
|
54
59
|
'Keep goal and success_criteria short and practical.',
|
|
@@ -59,6 +64,9 @@ const ANALYSIS_PROMPT_INSTRUCTIONS = [
|
|
|
59
64
|
'Do not suggest create_task, update_task, delete_task, or list_tasks for ordinary immediate work. Use task tools only when the user asks for future, recurring, scheduled, monitored, background, or existing-task management behavior.',
|
|
60
65
|
'Set parallel_work=true when independent tool calls or subagents can materially reduce latency.',
|
|
61
66
|
'Set completion_confidence_required="high" when wrong completion would be costly, state-changing, user-visible, or hard to recover.',
|
|
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.',
|
|
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.',
|
|
62
70
|
];
|
|
63
71
|
const PLAN_PROMPT_INSTRUCTIONS = [
|
|
64
72
|
'Create a concise execution plan for the current task.',
|
|
@@ -83,9 +91,14 @@ const VERIFIER_PROMPT_INSTRUCTIONS = [
|
|
|
83
91
|
'A successful create_task or update_task tool call is required before claiming a task schedule changed.',
|
|
84
92
|
'If external evidence conflicts with memory, history, or another tool result, preserve the uncertainty instead of flattening it into a single confident claim.',
|
|
85
93
|
'When the draft reply is already correct and fully supported by the evidence, return it unchanged. Do not rewrite for style.',
|
|
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.',
|
|
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.',
|
|
86
98
|
];
|
|
87
99
|
const EXECUTION_GUIDANCE_ACTION_LINES = [
|
|
88
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.',
|
|
101
|
+
'For research and multi-entity comparisons, cover each requested target with its own search/open path. Do not stop after one partial lead or invent the remaining devices from memory. Open primary sources before stating concrete specs.',
|
|
89
102
|
'For GitHub issue implementation or PR work, fetch the issue once, then establish or reuse a writable local checkout, create a task branch, inspect/edit/test locally, and push/open the PR. Use direct GitHub file mutation tools only as a fallback when a local checkout is unavailable.',
|
|
90
103
|
'Prefer the highest-level available tool for the job. If a tool accepts normal text, JSON, file paths, or line ranges, pass those directly instead of reconstructing equivalent data through shell commands.',
|
|
91
104
|
'Your shell (execute_command) starts in your workspace, and the file tools (read_file, read_files, write_file, edit_file, replace_file_range, list_directory, search_files) operate on that same workspace. Keep source checkouts and generated files in the shared workspace, then prefer file tools for inspection and edits instead of shell snippets. Clone a repo once and reuse it; do not re-clone or re-list the same tree.',
|
|
@@ -105,6 +118,7 @@ function buildVerifierSchemaExample(finalReply) {
|
|
|
105
118
|
evidence_sources: ['web_search'],
|
|
106
119
|
missing_evidence: [],
|
|
107
120
|
final_reply: finalReply || '',
|
|
121
|
+
safe_to_deliver: true,
|
|
108
122
|
confidence: 0.83,
|
|
109
123
|
};
|
|
110
124
|
}
|
|
@@ -250,6 +264,10 @@ function promoteAnalysisMode(initialMode, { verificationNeed, freshnessRisk, dra
|
|
|
250
264
|
function isDirectAnswerEligibleAnalysis(analysis) {
|
|
251
265
|
if (!analysis || typeof analysis !== 'object') return false;
|
|
252
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
|
+
: [];
|
|
253
271
|
const promotedMode = promoteAnalysisMode(analysis.mode, {
|
|
254
272
|
verificationNeed: analysis.verification_need,
|
|
255
273
|
freshnessRisk: analysis.freshness_risk,
|
|
@@ -257,10 +275,16 @@ function isDirectAnswerEligibleAnalysis(analysis) {
|
|
|
257
275
|
planningDepth: analysis.planning_depth,
|
|
258
276
|
});
|
|
259
277
|
|
|
278
|
+
// Direct answers are for zero-tool replies only. Any research targets or
|
|
279
|
+
// freshness/verification burden must enter the tool loop.
|
|
260
280
|
return promotedMode === 'direct_answer'
|
|
261
281
|
&& !analysis.needs_subagents
|
|
262
282
|
&& Boolean(draftReply)
|
|
263
|
-
&&
|
|
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';
|
|
264
288
|
}
|
|
265
289
|
|
|
266
290
|
function extractJsonCandidate(text) {
|
|
@@ -317,8 +341,25 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
317
341
|
'successCriteria',
|
|
318
342
|
TASK_ANALYSIS_SUCCESS_CRITERIA_LIMIT,
|
|
319
343
|
);
|
|
344
|
+
const researchTargets = resolveAliasedStringList(
|
|
345
|
+
raw,
|
|
346
|
+
fallback,
|
|
347
|
+
'research_targets',
|
|
348
|
+
'researchTargets',
|
|
349
|
+
TASK_ANALYSIS_SUCCESS_CRITERIA_LIMIT,
|
|
350
|
+
);
|
|
351
|
+
const declaredResearchDepth = pickEnum(
|
|
352
|
+
resolveAliasedValue(raw, fallback, 'research_depth', 'researchDepth', ''),
|
|
353
|
+
['none', 'light', 'deep'],
|
|
354
|
+
'',
|
|
355
|
+
);
|
|
320
356
|
|
|
321
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
|
+
);
|
|
322
363
|
const initialMode = pickEnum(
|
|
323
364
|
raw.mode || fallback.mode,
|
|
324
365
|
ANALYSIS_MODES,
|
|
@@ -364,13 +405,52 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
364
405
|
['none', 'possible', 'high'],
|
|
365
406
|
freshnessRiskFor({ verificationNeed })
|
|
366
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
|
+
}
|
|
367
422
|
|
|
368
|
-
|
|
423
|
+
let mode = promoteAnalysisMode(initialMode, {
|
|
369
424
|
verificationNeed,
|
|
370
425
|
freshnessRisk,
|
|
371
426
|
draftReply,
|
|
372
427
|
planningDepth,
|
|
373
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
|
+
}
|
|
374
454
|
|
|
375
455
|
const normalizedComplexity = pickEnum(
|
|
376
456
|
resolveAliasedValue(raw, fallback, 'complexity', 'complexity', ''),
|
|
@@ -385,19 +465,22 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
385
465
|
|
|
386
466
|
return {
|
|
387
467
|
mode,
|
|
388
|
-
freshness_risk:
|
|
389
|
-
verification_need:
|
|
390
|
-
planning_depth:
|
|
468
|
+
freshness_risk: effectiveFreshnessRisk,
|
|
469
|
+
verification_need: effectiveVerificationNeed,
|
|
470
|
+
planning_depth: mode === 'plan_execute' ? 'deep' : mode === 'direct_answer' ? 'none' : 'light',
|
|
391
471
|
confidence: clampConfidence(
|
|
392
472
|
raw.confidence ?? fallback.confidence,
|
|
393
473
|
draftReply ? TASK_ANALYSIS_CONFIDENCE_WITH_DRAFT : TASK_ANALYSIS_CONFIDENCE_DEFAULT,
|
|
394
474
|
),
|
|
395
475
|
suggested_tools: suggestedTools,
|
|
396
476
|
needs_subagents: resolveAliasedBoolean(raw, fallback, 'needs_subagents', 'needsSubagents'),
|
|
397
|
-
needs_verification:
|
|
477
|
+
needs_verification: effectiveVerificationNeed !== 'none',
|
|
398
478
|
draft_reply: draftReply,
|
|
479
|
+
draft_status: draftStatus,
|
|
399
480
|
goal: resolveAliasedText(raw, fallback, 'goal', 'goal', ''),
|
|
400
481
|
success_criteria: successCriteria,
|
|
482
|
+
research_targets: researchTargets,
|
|
483
|
+
research_depth: researchDepth,
|
|
401
484
|
complexity: mode === 'plan_execute' ? 'complex' : normalizedComplexity,
|
|
402
485
|
autonomy_level: mode === 'plan_execute' ? 'high' : normalizedAutonomyLevel,
|
|
403
486
|
progress_update_policy: pickEnum(
|
|
@@ -409,7 +492,9 @@ function normalizeTaskAnalysis(raw = {}, fallback = {}) {
|
|
|
409
492
|
completion_confidence_required: pickEnum(
|
|
410
493
|
resolveAliasedValue(raw, fallback, 'completion_confidence_required', 'completionConfidenceRequired', ''),
|
|
411
494
|
COMPLETION_CONFIDENCE_LEVELS,
|
|
412
|
-
|
|
495
|
+
effectiveVerificationNeed === 'required' || mode === 'plan_execute' || researchTargets.length >= 2
|
|
496
|
+
? 'high'
|
|
497
|
+
: 'medium',
|
|
413
498
|
),
|
|
414
499
|
};
|
|
415
500
|
}
|
|
@@ -493,6 +578,8 @@ function normalizeVerificationResult(raw = {}, fallbackReply = '') {
|
|
|
493
578
|
if (status === 'verified' && missingEvidence.length > 0) {
|
|
494
579
|
status = 'insufficient_evidence';
|
|
495
580
|
}
|
|
581
|
+
const safeToDeliver = status === 'verified'
|
|
582
|
+
|| resolveAliasedBoolean(raw, null, 'safe_to_deliver', 'safeToDeliver', false);
|
|
496
583
|
|
|
497
584
|
return {
|
|
498
585
|
status,
|
|
@@ -505,7 +592,8 @@ function normalizeVerificationResult(raw = {}, fallbackReply = '') {
|
|
|
505
592
|
VERIFICATION_EVIDENCE_SOURCES_LIMIT,
|
|
506
593
|
),
|
|
507
594
|
missing_evidence: missingEvidence,
|
|
508
|
-
final_reply: finalReply,
|
|
595
|
+
final_reply: safeToDeliver ? finalReply : '',
|
|
596
|
+
safe_to_deliver: safeToDeliver,
|
|
509
597
|
confidence: clampConfidence(
|
|
510
598
|
raw.confidence,
|
|
511
599
|
status === 'verified' ? VERIFICATION_CONFIDENCE_VERIFIED : VERIFICATION_CONFIDENCE_DEFAULT,
|
|
@@ -567,6 +655,7 @@ function buildExecutionGuidance({ analysis, plan = null, capabilityHealth }) {
|
|
|
567
655
|
`Execution mode: ${analysis.mode}.`,
|
|
568
656
|
analysis.goal ? `Goal: ${analysis.goal}` : '',
|
|
569
657
|
formatBulletSection('Success criteria', analysis.success_criteria),
|
|
658
|
+
formatBulletSection('Research targets', analysis.research_targets),
|
|
570
659
|
analysis.suggested_tools?.length
|
|
571
660
|
? `Advisory tool suggestions: ${analysis.suggested_tools.join(', ')}`
|
|
572
661
|
: '',
|
|
@@ -200,6 +200,199 @@ function isSubstantiveProgressToolName(toolName = '') {
|
|
|
200
200
|
return true;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
function clampResearchText(value, maxChars = 220) {
|
|
204
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
205
|
+
if (!text) return '';
|
|
206
|
+
if (text.length <= maxChars) return text;
|
|
207
|
+
return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function uniqueResearchTokens(values = [], { limit = 12 } = {}) {
|
|
211
|
+
const seen = new Set();
|
|
212
|
+
const tokens = [];
|
|
213
|
+
for (const value of values) {
|
|
214
|
+
const token = String(value || '').replace(/\s+/g, ' ').trim();
|
|
215
|
+
if (!token) continue;
|
|
216
|
+
const key = token.toLowerCase();
|
|
217
|
+
if (seen.has(key)) continue;
|
|
218
|
+
seen.add(key);
|
|
219
|
+
tokens.push(token);
|
|
220
|
+
if (tokens.length >= limit) break;
|
|
221
|
+
}
|
|
222
|
+
return tokens;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Research targets come from structured task analysis / goal contract only.
|
|
226
|
+
// Do not NLP-extract entity names from free text.
|
|
227
|
+
function collectResearchTargets(analysis = null, goalContext = null) {
|
|
228
|
+
return uniqueResearchTokens([
|
|
229
|
+
...(Array.isArray(analysis?.research_targets) ? analysis.research_targets : []),
|
|
230
|
+
...(Array.isArray(goalContext?.researchTargets) ? goalContext.researchTargets : []),
|
|
231
|
+
], { limit: 8 });
|
|
232
|
+
}
|
|
233
|
+
|
|
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
|
+
|
|
248
|
+
const mode = String(analysis?.mode || '').trim().toLowerCase();
|
|
249
|
+
const complexity = String(
|
|
250
|
+
goalContext?.effectiveComplexity
|
|
251
|
+
|| analysis?.complexity
|
|
252
|
+
|| '',
|
|
253
|
+
).trim().toLowerCase();
|
|
254
|
+
const autonomyLevel = String(
|
|
255
|
+
goalContext?.effectiveAutonomyLevel
|
|
256
|
+
|| analysis?.autonomy_level
|
|
257
|
+
|| '',
|
|
258
|
+
).trim().toLowerCase();
|
|
259
|
+
const completionConfidence = String(
|
|
260
|
+
goalContext?.effectiveCompletionConfidence
|
|
261
|
+
|| analysis?.completion_confidence_required
|
|
262
|
+
|| '',
|
|
263
|
+
).trim().toLowerCase();
|
|
264
|
+
const verificationNeed = String(analysis?.verification_need || '').trim().toLowerCase();
|
|
265
|
+
const freshnessRisk = String(analysis?.freshness_risk || '').trim().toLowerCase();
|
|
266
|
+
const planningDepth = String(analysis?.planning_depth || '').trim().toLowerCase();
|
|
267
|
+
const targets = declaredTargets;
|
|
268
|
+
|
|
269
|
+
if (mode === 'direct_answer' && verificationNeed === 'none' && freshnessRisk === 'none') {
|
|
270
|
+
return 'none';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const needsExternalEvidence = (
|
|
274
|
+
targets.length > 0
|
|
275
|
+
|| freshnessRisk === 'possible'
|
|
276
|
+
|| freshnessRisk === 'high'
|
|
277
|
+
);
|
|
278
|
+
if (!needsExternalEvidence) {
|
|
279
|
+
return 'none';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const deepSignals = [
|
|
283
|
+
mode === 'plan_execute',
|
|
284
|
+
complexity === 'complex',
|
|
285
|
+
autonomyLevel === 'high',
|
|
286
|
+
completionConfidence === 'high',
|
|
287
|
+
verificationNeed === 'required',
|
|
288
|
+
freshnessRisk === 'high',
|
|
289
|
+
planningDepth === 'deep',
|
|
290
|
+
targets.length >= 2,
|
|
291
|
+
].filter(Boolean).length;
|
|
292
|
+
|
|
293
|
+
if (deepSignals >= 2 || targets.length >= 2 || verificationNeed === 'required' || freshnessRisk === 'high') {
|
|
294
|
+
return 'deep';
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return 'light';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function isSuccessfulResearchExecution(item = {}) {
|
|
301
|
+
if (!item || item.ok !== true) return false;
|
|
302
|
+
if (!isSubstantiveProgressToolName(item.toolName)) return false;
|
|
303
|
+
if (item.evidenceSource === 'messaging') return false;
|
|
304
|
+
return Boolean(item.evidenceRelevant || item.dependsOnOutput || item.stateChanged);
|
|
305
|
+
}
|
|
306
|
+
|
|
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
|
+
];
|
|
318
|
+
}
|
|
319
|
+
|
|
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');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function researchExecutionSignature(execution = {}) {
|
|
329
|
+
return JSON.stringify([
|
|
330
|
+
String(execution.toolName || '').trim(),
|
|
331
|
+
execution.input && typeof execution.input === 'object' ? execution.input : {},
|
|
332
|
+
]);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function assessResearchAdequacy({
|
|
336
|
+
analysis = null,
|
|
337
|
+
goalContext = null,
|
|
338
|
+
toolExecutions = [],
|
|
339
|
+
} = {}) {
|
|
340
|
+
const intensity = resolveResearchIntensity(analysis, goalContext);
|
|
341
|
+
const targets = collectResearchTargets(analysis, goalContext);
|
|
342
|
+
|
|
343
|
+
const successful = (Array.isArray(toolExecutions) ? toolExecutions : [])
|
|
344
|
+
.filter(isSuccessfulResearchExecution);
|
|
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.'];
|
|
352
|
+
const nextActions = [];
|
|
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.');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
intensity,
|
|
361
|
+
adequate: structurallyReady,
|
|
362
|
+
structurallyReady,
|
|
363
|
+
semanticReviewRequired: intensity !== 'none',
|
|
364
|
+
evidenceCandidateCount: successful.length,
|
|
365
|
+
uniqueEvidenceCandidateCount,
|
|
366
|
+
targets,
|
|
367
|
+
coveredTargets: [],
|
|
368
|
+
primaryCoveredTargets: [],
|
|
369
|
+
uncoveredTargets: intensity === 'none' ? [] : targets,
|
|
370
|
+
primaryUncoveredTargets: intensity === 'none' ? [] : targets,
|
|
371
|
+
missing,
|
|
372
|
+
nextActions,
|
|
373
|
+
reason: structurallyReady
|
|
374
|
+
? (intensity === 'none'
|
|
375
|
+
? 'No research burden for this run.'
|
|
376
|
+
: 'Source candidates exist; the AI completion judge must still validate target coverage and source quality.')
|
|
377
|
+
: clampResearchText(missing.join(' ') || 'Research evidence is still incomplete.', 320),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function formatResearchAdequacyGuidance(assessment = null) {
|
|
382
|
+
if (!assessment || assessment.intensity === 'none') return '';
|
|
383
|
+
const lines = [
|
|
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.',
|
|
387
|
+
assessment.reason ? `Gap: ${assessment.reason}` : '',
|
|
388
|
+
assessment.nextActions?.length
|
|
389
|
+
? `Next safe steps:\n- ${assessment.nextActions.join('\n- ')}`
|
|
390
|
+
: '',
|
|
391
|
+
'The completion judge must explicitly map every requested target to supporting evidence and judge whether that evidence is primary, secondary, or merely contextual.',
|
|
392
|
+
];
|
|
393
|
+
return lines.filter(Boolean).join('\n');
|
|
394
|
+
}
|
|
395
|
+
|
|
203
396
|
function isSubstantiveProgressEvidence(item = {}) {
|
|
204
397
|
if (!isSubstantiveProgressToolName(item.toolName)) return false;
|
|
205
398
|
if (item.evidenceSource === 'messaging') return false;
|
|
@@ -274,9 +467,14 @@ function buildAutonomousRecoveryContext({ err, toolExecutions = [], tools = [],
|
|
|
274
467
|
module.exports = {
|
|
275
468
|
classifyToolExecution,
|
|
276
469
|
deriveEvidenceSource,
|
|
470
|
+
assessResearchAdequacy,
|
|
471
|
+
formatResearchAdequacyGuidance,
|
|
277
472
|
gatheredNewEvidence,
|
|
278
473
|
isSubstantiveProgressEvidence,
|
|
279
474
|
isSubstantiveProgressToolName,
|
|
475
|
+
resolveResearchIntensity,
|
|
476
|
+
selectResearchEvidenceCandidates,
|
|
477
|
+
summarizeResearchEvidenceCatalog,
|
|
280
478
|
summarizeProgressToolExecutions,
|
|
281
479
|
summarizeToolExecutions,
|
|
282
480
|
summarizeAvailableTools,
|
|
@@ -12,7 +12,6 @@ const {
|
|
|
12
12
|
normalizeOutgoingMessageForPlatform,
|
|
13
13
|
} = require('../messaging/formatting_guides');
|
|
14
14
|
const { INTERIM_KINDS, normalizeInterimKind } = require('./interim');
|
|
15
|
-
const { isDeferredWorkReply } = require('./terminal_reply');
|
|
16
15
|
const { normalizeWhatsAppId } = require('../../utils/whatsapp');
|
|
17
16
|
const {
|
|
18
17
|
executeIntegratedTool,
|
|
@@ -2401,6 +2400,13 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2401
2400
|
if (!engine || !runId) {
|
|
2402
2401
|
return { error: 'Interim updates require an active run.' };
|
|
2403
2402
|
}
|
|
2403
|
+
if (engine.getRunMeta(runId)?.messagingContext?.behavior?.isGroup === true) {
|
|
2404
|
+
return {
|
|
2405
|
+
sent: false,
|
|
2406
|
+
skipped: true,
|
|
2407
|
+
reason: 'Interim progress updates are suppressed in shared rooms.',
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2404
2410
|
const interimContent = typeof args.content === 'string' ? args.content : '';
|
|
2405
2411
|
const expectsReply = args.expects_reply === true;
|
|
2406
2412
|
const deferFollowUp = args.defer_follow_up === true;
|
|
@@ -2438,16 +2444,6 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2438
2444
|
platform: args.platform,
|
|
2439
2445
|
to: args.to,
|
|
2440
2446
|
});
|
|
2441
|
-
if (
|
|
2442
|
-
!suppressReply
|
|
2443
|
-
&& triggerSource === 'messaging'
|
|
2444
|
-
&& originDelivery
|
|
2445
|
-
&& isDeferredWorkReply(normalizedMessage)
|
|
2446
|
-
) {
|
|
2447
|
-
return {
|
|
2448
|
-
error: 'send_message cannot end the run with a promise or progress-only reply. Continue the work, or use send_interim_update for a factual interim update.',
|
|
2449
|
-
};
|
|
2450
|
-
}
|
|
2451
2447
|
if (isProactiveTrigger(triggerSource)) {
|
|
2452
2448
|
const proactiveValidation = validateProactiveSendMessageArgs({
|
|
2453
2449
|
purpose: args.purpose,
|
|
@@ -2506,13 +2502,58 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2506
2502
|
if (triggerSource === 'messaging' && originDelivery) {
|
|
2507
2503
|
await engine?.stopMessagingProgressSupervisor?.(runId);
|
|
2508
2504
|
}
|
|
2509
|
-
const
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2505
|
+
const behavior = runState?.messagingContext?.behavior;
|
|
2506
|
+
const behaviorPipeline = app?.locals?.behaviorPipeline;
|
|
2507
|
+
let deliveredContent = normalizedMessage;
|
|
2508
|
+
let sendResult;
|
|
2509
|
+
if (
|
|
2510
|
+
originDelivery
|
|
2511
|
+
&& behavior
|
|
2512
|
+
&& behavior.enabled !== false
|
|
2513
|
+
&& behaviorPipeline
|
|
2514
|
+
&& typeof behaviorPipeline.refineAndMaybeDeliver === 'function'
|
|
2515
|
+
) {
|
|
2516
|
+
const behaviorResult = await behaviorPipeline.refineAndMaybeDeliver({
|
|
2517
|
+
userId,
|
|
2518
|
+
agentId,
|
|
2519
|
+
msg: behavior.message,
|
|
2520
|
+
config: behavior.config,
|
|
2521
|
+
draft: normalizedMessage,
|
|
2522
|
+
messagingManager: manager,
|
|
2523
|
+
runId,
|
|
2524
|
+
signal,
|
|
2525
|
+
mediaPath: args.media_path,
|
|
2526
|
+
turnEpoch: behavior.turnEpoch,
|
|
2527
|
+
deliver: true,
|
|
2528
|
+
});
|
|
2529
|
+
deliveredContent = behaviorResult.content;
|
|
2530
|
+
if (behaviorResult.delivered) {
|
|
2531
|
+
sendResult = { success: true, behavior: true, result: behaviorResult.delivery };
|
|
2532
|
+
} else if (behaviorResult.suppressed) {
|
|
2533
|
+
markProactiveNoResponse({ runState, deliveryState });
|
|
2534
|
+
sendResult = {
|
|
2535
|
+
success: true,
|
|
2536
|
+
sent: false,
|
|
2537
|
+
suppressed: true,
|
|
2538
|
+
reason: behaviorResult.reasonCodes?.[0] || 'behavior_suppressed',
|
|
2539
|
+
};
|
|
2540
|
+
} else {
|
|
2541
|
+
sendResult = {
|
|
2542
|
+
success: false,
|
|
2543
|
+
error: behaviorResult.delivery?.error
|
|
2544
|
+
|| behaviorResult.delivery?.reason
|
|
2545
|
+
|| 'Behavior delivery was not confirmed.',
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
} else {
|
|
2549
|
+
sendResult = await manager.sendMessage(userId, args.platform, args.to, args.content, {
|
|
2550
|
+
agentId,
|
|
2551
|
+
mediaPath: args.media_path,
|
|
2552
|
+
runId,
|
|
2553
|
+
persistConversation: triggerSource === 'schedule' || triggerSource === 'tasks',
|
|
2554
|
+
signal,
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2516
2557
|
// Track that the agent explicitly sent a message during this run
|
|
2517
2558
|
if (
|
|
2518
2559
|
!suppressReply
|
|
@@ -2520,7 +2561,7 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2520
2561
|
&& sendResult?.suppressed !== true
|
|
2521
2562
|
&& originDelivery
|
|
2522
2563
|
) {
|
|
2523
|
-
markProactiveMessageSent({ runState, deliveryState, content:
|
|
2564
|
+
markProactiveMessageSent({ runState, deliveryState, content: deliveredContent });
|
|
2524
2565
|
if (runState && triggerSource === 'messaging') {
|
|
2525
2566
|
runState.explicitMessageSent = true;
|
|
2526
2567
|
}
|