neoagent 3.2.1-beta.0 → 3.2.1-beta.10

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.
Files changed (175) hide show
  1. package/docs/agent-run-lifecycle.md +10 -0
  2. package/extensions/chrome-browser/background.mjs +318 -88
  3. package/extensions/chrome-browser/http.mjs +136 -0
  4. package/extensions/chrome-browser/protocol.mjs +654 -90
  5. package/flutter_app/lib/main_chat.dart +118 -739
  6. package/flutter_app/lib/main_controller.dart +111 -20
  7. package/flutter_app/lib/main_integrations.dart +607 -8
  8. package/flutter_app/lib/main_models.dart +3 -0
  9. package/flutter_app/lib/main_operations.dart +334 -321
  10. package/flutter_app/lib/main_security.dart +266 -112
  11. package/flutter_app/lib/main_settings.dart +4 -3
  12. package/flutter_app/lib/main_shared.dart +14 -11
  13. package/flutter_app/lib/src/backend_client.dart +78 -0
  14. package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
  15. package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
  16. package/flutter_app/windows/runner/flutter_window.cpp +143 -32
  17. package/landing/index.html +3 -1
  18. package/lib/manager.js +106 -89
  19. package/lib/schema_migrations.js +145 -13
  20. package/package.json +30 -15
  21. package/runtime/paths.js +49 -5
  22. package/server/db/database.js +4 -4
  23. package/server/guest-agent.cli.package.json +13 -0
  24. package/server/guest_agent.js +85 -40
  25. package/server/http/middleware.js +24 -0
  26. package/server/http/routes.js +11 -6
  27. package/server/public/.last_build_id +1 -1
  28. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  29. package/server/public/flutter_bootstrap.js +2 -2
  30. package/server/public/main.dart.js +73083 -72209
  31. package/server/routes/admin.js +1 -1
  32. package/server/routes/agents.js +35 -2
  33. package/server/routes/android.js +30 -34
  34. package/server/routes/browser.js +23 -15
  35. package/server/routes/desktop.js +18 -1
  36. package/server/routes/integrations.js +107 -1
  37. package/server/routes/memory.js +1 -0
  38. package/server/routes/settings.js +16 -5
  39. package/server/routes/social_reach.js +12 -3
  40. package/server/routes/social_video.js +4 -0
  41. package/server/services/agents/manager.js +1 -1
  42. package/server/services/ai/capabilityHealth.js +62 -96
  43. package/server/services/ai/compaction.js +7 -2
  44. package/server/services/ai/history.js +45 -6
  45. package/server/services/ai/integrated_tools/http_request.js +8 -0
  46. package/server/services/ai/loop/agent_engine_core.js +496 -166
  47. package/server/services/ai/loop/blank_recovery.js +5 -4
  48. package/server/services/ai/loop/callbacks.js +1 -0
  49. package/server/services/ai/loop/completion_judge.js +121 -5
  50. package/server/services/ai/loop/conversation_loop.js +620 -340
  51. package/server/services/ai/loop/lifecycle.js +108 -0
  52. package/server/services/ai/loop/messaging_delivery.js +129 -57
  53. package/server/services/ai/loop/model_call_guard.js +91 -0
  54. package/server/services/ai/loop/model_io.js +48 -56
  55. package/server/services/ai/loop/progress_classification.js +2 -0
  56. package/server/services/ai/loop/tool_dispatch.js +28 -8
  57. package/server/services/ai/loopPolicy.js +48 -21
  58. package/server/services/ai/messagingFallback.js +17 -17
  59. package/server/services/ai/model_discovery.js +227 -0
  60. package/server/services/ai/model_failure_cache.js +108 -0
  61. package/server/services/ai/model_identity.js +71 -0
  62. package/server/services/ai/models.js +68 -163
  63. package/server/services/ai/providerRetry.js +17 -59
  64. package/server/services/ai/provider_selector.js +166 -0
  65. package/server/services/ai/providers/anthropic.js +4 -4
  66. package/server/services/ai/providers/claudeCode.js +21 -33
  67. package/server/services/ai/providers/githubCopilot.js +41 -20
  68. package/server/services/ai/providers/google.js +135 -97
  69. package/server/services/ai/providers/grok.js +6 -5
  70. package/server/services/ai/providers/grokOauth.js +19 -27
  71. package/server/services/ai/providers/nvidia.js +12 -7
  72. package/server/services/ai/providers/ollama.js +114 -86
  73. package/server/services/ai/providers/ollama_stream.js +142 -0
  74. package/server/services/ai/providers/openai.js +41 -7
  75. package/server/services/ai/providers/openaiCodex.js +13 -4
  76. package/server/services/ai/providers/openrouter.js +31 -9
  77. package/server/services/ai/providers/provider_error.js +36 -0
  78. package/server/services/ai/settings.js +26 -2
  79. package/server/services/ai/systemPrompt.js +19 -12
  80. package/server/services/ai/taskAnalysis.js +58 -10
  81. package/server/services/ai/terminal_reply.js +18 -0
  82. package/server/services/ai/toolEvidence.js +350 -29
  83. package/server/services/ai/tools.js +190 -111
  84. package/server/services/android/controller.js +770 -237
  85. package/server/services/android/process.js +140 -0
  86. package/server/services/android/sdk_download.js +143 -0
  87. package/server/services/android/uia.js +6 -5
  88. package/server/services/artifacts/store.js +24 -0
  89. package/server/services/browser/controller.js +843 -385
  90. package/server/services/browser/extension/gateway.js +40 -16
  91. package/server/services/browser/extension/protocol.js +15 -1
  92. package/server/services/browser/extension/provider.js +71 -47
  93. package/server/services/browser/extension/registry.js +155 -34
  94. package/server/services/cli/executor.js +62 -9
  95. package/server/services/credentials/bitwarden_cli.js +322 -0
  96. package/server/services/credentials/broker.js +594 -0
  97. package/server/services/desktop/gateway.js +41 -4
  98. package/server/services/desktop/protocol.js +3 -0
  99. package/server/services/desktop/provider.js +39 -42
  100. package/server/services/desktop/registry.js +137 -52
  101. package/server/services/integrations/bitwarden/constants.js +14 -0
  102. package/server/services/integrations/bitwarden/provider.js +197 -0
  103. package/server/services/integrations/bitwarden/snapshot.js +65 -0
  104. package/server/services/integrations/figma/provider.js +78 -12
  105. package/server/services/integrations/github/common.js +11 -6
  106. package/server/services/integrations/github/provider.js +52 -53
  107. package/server/services/integrations/google/provider.js +55 -19
  108. package/server/services/integrations/home_assistant/network.js +17 -20
  109. package/server/services/integrations/home_assistant/provider.js +7 -5
  110. package/server/services/integrations/home_assistant/tools.js +17 -5
  111. package/server/services/integrations/http.js +51 -0
  112. package/server/services/integrations/manager.js +159 -53
  113. package/server/services/integrations/microsoft/provider.js +80 -13
  114. package/server/services/integrations/neoarchive/provider.js +55 -29
  115. package/server/services/integrations/neorecall/client.js +17 -10
  116. package/server/services/integrations/neorecall/provider.js +20 -11
  117. package/server/services/integrations/notion/provider.js +16 -13
  118. package/server/services/integrations/oauth_provider.js +115 -51
  119. package/server/services/integrations/registry.js +2 -0
  120. package/server/services/integrations/slack/provider.js +98 -9
  121. package/server/services/integrations/spotify/provider.js +67 -71
  122. package/server/services/integrations/trello/provider.js +21 -7
  123. package/server/services/integrations/weather/provider.js +18 -12
  124. package/server/services/integrations/whatsapp/provider.js +76 -16
  125. package/server/services/manager.js +110 -1
  126. package/server/services/memory/embedding_index.js +20 -8
  127. package/server/services/memory/embeddings.js +151 -90
  128. package/server/services/memory/ingestion.js +50 -9
  129. package/server/services/memory/ingestion_documents.js +13 -3
  130. package/server/services/memory/manager.js +52 -19
  131. package/server/services/messaging/automation.js +85 -10
  132. package/server/services/messaging/formatting_guides.js +7 -4
  133. package/server/services/messaging/http_platforms.js +33 -13
  134. package/server/services/messaging/inbound_queue.js +78 -24
  135. package/server/services/messaging/inbound_store.js +224 -0
  136. package/server/services/messaging/manager.js +326 -51
  137. package/server/services/messaging/typing_keepalive.js +5 -2
  138. package/server/services/messaging/whatsapp.js +22 -14
  139. package/server/services/network/http.js +210 -0
  140. package/server/services/network/safe_request.js +307 -0
  141. package/server/services/runtime/backends/local-vm.js +227 -67
  142. package/server/services/runtime/docker-vm-manager.js +9 -0
  143. package/server/services/runtime/guest_bootstrap.js +30 -4
  144. package/server/services/runtime/guest_image.js +43 -12
  145. package/server/services/runtime/manager.js +77 -23
  146. package/server/services/runtime/validation.js +7 -6
  147. package/server/services/security/tool_categories.js +6 -0
  148. package/server/services/social_reach/channels/github.js +10 -4
  149. package/server/services/social_reach/channels/reddit.js +4 -4
  150. package/server/services/social_reach/channels/rss.js +2 -2
  151. package/server/services/social_reach/channels/social_video.js +12 -7
  152. package/server/services/social_reach/channels/v2ex.js +21 -8
  153. package/server/services/social_reach/channels/x.js +2 -2
  154. package/server/services/social_reach/channels/xueqiu.js +5 -5
  155. package/server/services/social_reach/service.js +9 -6
  156. package/server/services/social_reach/utils.js +65 -14
  157. package/server/services/social_video/service.js +160 -50
  158. package/server/services/tasks/integration_runtime.js +18 -8
  159. package/server/services/tasks/runtime.js +39 -4
  160. package/server/services/voice/agentBridge.js +17 -4
  161. package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
  162. package/server/services/voice/liveSession.js +31 -0
  163. package/server/services/voice/message.js +1 -1
  164. package/server/services/voice/openaiSpeech.js +33 -8
  165. package/server/services/voice/providers.js +233 -151
  166. package/server/services/voice/runtime.js +2 -2
  167. package/server/services/voice/runtimeManager.js +118 -20
  168. package/server/services/voice/turnRunner.js +6 -0
  169. package/server/services/wearable/firmware_manifest.js +51 -13
  170. package/server/services/wearable/service.js +1 -0
  171. package/server/utils/abort.js +96 -0
  172. package/server/utils/cloud-security.js +110 -3
  173. package/server/utils/files.js +31 -0
  174. package/server/utils/image_payload.js +95 -0
  175. package/server/utils/retry.js +107 -0
@@ -34,30 +34,50 @@ const EVIDENCE_SOURCE_RULES = [
34
34
  { source: 'subagent', match: (name) => name.includes('subagent') },
35
35
  ];
36
36
 
37
+ const STATE_CHANGING_DEVICE_TOOLS = new Set([
38
+ 'android_install_apk',
39
+ 'android_long_press',
40
+ 'android_open_app',
41
+ 'android_open_intent',
42
+ 'android_press_key',
43
+ 'android_start_emulator',
44
+ 'android_stop_emulator',
45
+ 'android_swipe',
46
+ 'android_tap',
47
+ 'android_type',
48
+ 'desktop_click',
49
+ 'desktop_drag',
50
+ 'desktop_launch_app',
51
+ 'desktop_press_key',
52
+ 'desktop_scroll',
53
+ 'desktop_select_device',
54
+ 'desktop_type',
55
+ ]);
56
+
37
57
  function deriveEvidenceSource(name) {
38
58
  const rule = EVIDENCE_SOURCE_RULES.find((entry) => entry.match(name));
39
59
  return rule ? rule.source : 'tool';
40
60
  }
41
61
 
42
- function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = '') {
62
+ function resolveDeclaredToolAccess(toolDefinition, toolArgs = {}) {
63
+ const access = String(toolDefinition?.access || '').trim().toLowerCase();
64
+ if (access === 'read' || access === 'write') return access;
65
+ if (access !== 'dynamic_http_method') return null;
66
+
67
+ const method = String(toolArgs?.method || toolArgs?.http_method || 'GET')
68
+ .trim()
69
+ .toUpperCase();
70
+ return ['GET', 'HEAD', 'OPTIONS'].includes(method) ? 'read' : 'write';
71
+ }
72
+
73
+ function classifyToolExecution(
74
+ toolName,
75
+ toolArgs = {},
76
+ result,
77
+ errorMessage = '',
78
+ toolDefinition = null,
79
+ ) {
43
80
  const name = String(toolName || '');
44
- const evidenceRelevantPrefixes = ['browser_', 'android_'];
45
- const evidenceRelevantExact = new Set([
46
- 'web_search',
47
- 'http_request',
48
- 'read_file',
49
- 'read_files',
50
- 'search_files',
51
- 'list_directory',
52
- 'code_navigate',
53
- 'query_structured_data',
54
- 'session_search',
55
- 'memory_recall',
56
- 'analyze_image',
57
- 'read_health_data',
58
- 'list_tasks',
59
- 'wait_subagent',
60
- ]);
61
81
  const stateChangingExact = new Set([
62
82
  'execute_command',
63
83
  'write_file',
@@ -79,22 +99,30 @@ function classifyToolExecution(toolName, toolArgs = {}, result, errorMessage = '
79
99
  'mcp_add_server',
80
100
  'mcp_remove_server',
81
101
  'spawn_subagent',
102
+ 'delegate_to_agent',
82
103
  'cancel_subagent',
83
104
  ]);
84
105
 
85
- const evidenceSource = deriveEvidenceSource(name);
106
+ const declaredAccess = resolveDeclaredToolAccess(toolDefinition, toolArgs);
107
+ const evidenceSource = declaredAccess ? 'integration' : deriveEvidenceSource(name);
86
108
 
87
- const evidenceRelevant = evidenceRelevantExact.has(name)
88
- || evidenceRelevantPrefixes.some((prefix) => name.startsWith(prefix));
89
- const stateChanged = (
90
- name === 'execute_command'
91
- ? !isClearlyReadOnlyShellCommand(toolArgs?.command || '')
92
- : stateChangingExact.has(name)
93
- )
109
+ // Any successful, substantive tool result can advance the run. This default
110
+ // is deliberate: MCP, skills, and newly added integrations must not become
111
+ // invisible to the churn guard just because their names were not added to a
112
+ // central allow-list. Repetition detection still rejects unchanged retries.
113
+ const evidenceRelevant = isSubstantiveProgressToolName(name);
114
+ let directStateChange;
115
+ if (name === 'execute_command' || name === 'android_shell') {
116
+ directStateChange = !isClearlyReadOnlyShellCommand(toolArgs?.command || '');
117
+ } else {
118
+ directStateChange = declaredAccess === 'write'
119
+ || STATE_CHANGING_DEVICE_TOOLS.has(name)
120
+ || stateChangingExact.has(name);
121
+ }
122
+ const stateChanged = directStateChange
94
123
  || (name.startsWith('github_') && isProgressToolCall(name, toolArgs))
95
124
  || (name === 'http_request' && isProgressToolCall(name, toolArgs))
96
- || name.startsWith('android_')
97
- || ['browser_click', 'browser_type', 'browser_evaluate'].includes(name);
125
+ || ['browser_click', 'browser_evaluate', 'browser_navigate', 'browser_type'].includes(name);
98
126
 
99
127
  let normalizedError = String(errorMessage || result?.error || '').trim();
100
128
  if (!normalizedError && name === 'execute_command' && result && typeof result === 'object') {
@@ -167,11 +195,300 @@ function gatheredNewEvidence(execution, repetitionObservation = null) {
167
195
  function isSubstantiveProgressToolName(toolName = '') {
168
196
  const name = String(toolName || '').trim();
169
197
  if (!name) return false;
170
- if (name === 'send_message' || name === 'send_interim_update' || name === 'make_call') return false;
198
+ if (name === 'send_message' || name === 'send_interim_update' || name === 'make_call' || name === 'notify_user') return false;
171
199
  if (name === 'think' || name === 'activate_tools' || name === 'task_complete') return false;
172
200
  return true;
173
201
  }
174
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
+ // Research targets come from structured task analysis / goal contract only.
261
+ // Do not NLP-extract entity names from free text.
262
+ function collectResearchTargets(analysis = null, goalContext = null) {
263
+ return uniqueResearchTokens([
264
+ ...(Array.isArray(analysis?.research_targets) ? analysis.research_targets : []),
265
+ ...(Array.isArray(goalContext?.researchTargets) ? goalContext.researchTargets : []),
266
+ ], { limit: 8 });
267
+ }
268
+
269
+ function hasResearchToolHint(analysis = null) {
270
+ const tools = Array.isArray(analysis?.suggested_tools) ? analysis.suggested_tools : [];
271
+ return tools.some((name) => {
272
+ const tool = String(name || '').trim().toLowerCase();
273
+ if (!tool) return false;
274
+ if (RESEARCH_TOOL_HINTS.has(tool)) return true;
275
+ // Tool-schema identity only: family prefixes, not free-text phrase matching.
276
+ return tool.startsWith('browser_') || tool.startsWith('mcp_');
277
+ });
278
+ }
279
+
280
+ function resolveResearchIntensity(analysis = null, goalContext = null) {
281
+ const mode = String(analysis?.mode || '').trim().toLowerCase();
282
+ const complexity = String(
283
+ goalContext?.effectiveComplexity
284
+ || analysis?.complexity
285
+ || '',
286
+ ).trim().toLowerCase();
287
+ const autonomyLevel = String(
288
+ goalContext?.effectiveAutonomyLevel
289
+ || analysis?.autonomy_level
290
+ || '',
291
+ ).trim().toLowerCase();
292
+ const completionConfidence = String(
293
+ goalContext?.effectiveCompletionConfidence
294
+ || analysis?.completion_confidence_required
295
+ || '',
296
+ ).trim().toLowerCase();
297
+ const verificationNeed = String(analysis?.verification_need || '').trim().toLowerCase();
298
+ const freshnessRisk = String(analysis?.freshness_risk || '').trim().toLowerCase();
299
+ const planningDepth = String(analysis?.planning_depth || '').trim().toLowerCase();
300
+ const targets = collectResearchTargets(analysis, goalContext);
301
+ const researchToolHint = hasResearchToolHint(analysis);
302
+
303
+ if (mode === 'direct_answer' && verificationNeed === 'none' && freshnessRisk === 'none') {
304
+ return 'none';
305
+ }
306
+
307
+ // External/source-backed work only. Pure local implementation tasks must not
308
+ // inherit a research burden just because they are complex or multi-step.
309
+ const needsExternalEvidence = (
310
+ targets.length > 0
311
+ || researchToolHint
312
+ || freshnessRisk === 'possible'
313
+ || freshnessRisk === 'high'
314
+ || verificationNeed === 'required'
315
+ );
316
+ if (!needsExternalEvidence) {
317
+ return 'none';
318
+ }
319
+
320
+ const deepSignals = [
321
+ mode === 'plan_execute',
322
+ complexity === 'complex',
323
+ autonomyLevel === 'high',
324
+ completionConfidence === 'high',
325
+ verificationNeed === 'required',
326
+ freshnessRisk === 'high',
327
+ planningDepth === 'deep',
328
+ targets.length >= 2,
329
+ ].filter(Boolean).length;
330
+
331
+ if (deepSignals >= 2 || targets.length >= 2 || verificationNeed === 'required' || freshnessRisk === 'high') {
332
+ return 'deep';
333
+ }
334
+
335
+ return 'light';
336
+ }
337
+
338
+ function isSuccessfulResearchExecution(item = {}) {
339
+ if (!item || item.ok !== true) return false;
340
+ if (!isSubstantiveProgressToolName(item.toolName)) return false;
341
+ if (item.evidenceSource === 'messaging') return false;
342
+ return Boolean(item.evidenceRelevant || item.dependsOnOutput || item.stateChanged);
343
+ }
344
+
345
+ function isPrimaryResearchSource(source = '') {
346
+ return PRIMARY_RESEARCH_SOURCES.has(String(source || '').trim().toLowerCase());
347
+ }
348
+
349
+ function isSecondaryResearchSource(source = '') {
350
+ return SECONDARY_RESEARCH_SOURCES.has(String(source || '').trim().toLowerCase());
351
+ }
352
+
353
+ function executionMentionsTarget(execution = {}, target = '') {
354
+ const needle = String(target || '').trim().toLowerCase();
355
+ if (!needle) return false;
356
+ const haystack = [
357
+ execution.summary,
358
+ execution.toolName,
359
+ execution.evidenceSource,
360
+ JSON.stringify(execution.input || {}),
361
+ ].join(' ').toLowerCase();
362
+ if (haystack.includes(needle)) return true;
363
+
364
+ const tokens = needle
365
+ .split(/[^a-z0-9+]+/i)
366
+ .map((token) => token.trim())
367
+ .filter((token) => token.length >= 3);
368
+ if (tokens.length === 0) return false;
369
+ const matched = tokens.filter((token) => haystack.includes(token)).length;
370
+ return matched >= Math.min(2, tokens.length);
371
+ }
372
+
373
+ function assessResearchAdequacy({
374
+ analysis = null,
375
+ goalContext = null,
376
+ toolExecutions = [],
377
+ } = {}) {
378
+ const intensity = resolveResearchIntensity(analysis, goalContext);
379
+ const targets = collectResearchTargets(analysis, goalContext);
380
+
381
+ const successful = (Array.isArray(toolExecutions) ? toolExecutions : [])
382
+ .filter(isSuccessfulResearchExecution);
383
+ const primary = successful.filter((item) => isPrimaryResearchSource(item.evidenceSource));
384
+ const secondary = successful.filter((item) => isSecondaryResearchSource(item.evidenceSource));
385
+ const coveredTargets = targets.filter((target) => (
386
+ successful.some((item) => executionMentionsTarget(item, target))
387
+ ));
388
+ const primaryCoveredTargets = targets.filter((target) => (
389
+ primary.some((item) => executionMentionsTarget(item, target))
390
+ ));
391
+ const uncoveredTargets = targets.filter((target) => !coveredTargets.includes(target));
392
+ const primaryUncoveredTargets = targets.filter((target) => !primaryCoveredTargets.includes(target));
393
+
394
+ const requiredPrimarySources = intensity === 'deep'
395
+ ? (targets.length > 0 ? Math.min(4, targets.length) : 2)
396
+ : intensity === 'light'
397
+ ? 1
398
+ : 0;
399
+ const requiredSecondarySources = intensity === 'deep' ? 1 : 0;
400
+ const requiredTargetCoverage = intensity === 'deep'
401
+ ? targets.length
402
+ : intensity === 'light'
403
+ ? Math.min(targets.length, 1)
404
+ : 0;
405
+ const requiredPrimaryTargetCoverage = intensity === 'deep'
406
+ ? targets.length
407
+ : 0;
408
+
409
+ const missing = [];
410
+ if (primary.length < requiredPrimarySources) {
411
+ missing.push(
412
+ `Need ${requiredPrimarySources} primary source open/fetch/inspect step(s); have ${primary.length}.`,
413
+ );
414
+ }
415
+ if (secondary.length < requiredSecondarySources && primary.length < requiredPrimarySources) {
416
+ missing.push('Need at least one search lead before finishing deep research.');
417
+ }
418
+ if (targets.length > 0 && coveredTargets.length < requiredTargetCoverage) {
419
+ missing.push(
420
+ `Need evidence for: ${uncoveredTargets.slice(0, 4).join('; ') || targets.slice(0, 4).join('; ')}.`,
421
+ );
422
+ }
423
+ if (targets.length > 0 && primaryCoveredTargets.length < requiredPrimaryTargetCoverage) {
424
+ missing.push(
425
+ `Need primary-source evidence for: ${primaryUncoveredTargets.slice(0, 4).join('; ') || targets.slice(0, 4).join('; ')}.`,
426
+ );
427
+ }
428
+ if (intensity === 'deep' && primary.length === 0 && secondary.length > 0) {
429
+ missing.push('Search snippets alone are not enough; open primary sources for the key claims.');
430
+ }
431
+ if (intensity === 'light' && primary.length === 0 && secondary.length === 0) {
432
+ missing.push('Need at least one successful search or primary-source check before finishing.');
433
+ }
434
+
435
+ const adequate = intensity === 'none' ? true : missing.length === 0;
436
+ const nextActions = [];
437
+ if (!adequate) {
438
+ if (primaryUncoveredTargets.length > 0) {
439
+ nextActions.push(
440
+ `Open or fetch primary sources for each remaining target: ${primaryUncoveredTargets.slice(0, 4).join('; ')}.`,
441
+ );
442
+ } else if (uncoveredTargets.length > 0) {
443
+ nextActions.push(
444
+ `Research each remaining target separately: ${uncoveredTargets.slice(0, 4).join('; ')}.`,
445
+ );
446
+ }
447
+ if (primary.length < requiredPrimarySources) {
448
+ nextActions.push('Open or fetch primary pages/docs for the remaining claims instead of guessing from memory or snippets.');
449
+ }
450
+ if (secondary.length === 0 && intensity === 'deep') {
451
+ nextActions.push('Run targeted searches, then open the strongest sources.');
452
+ }
453
+ }
454
+
455
+ return {
456
+ intensity,
457
+ adequate,
458
+ requiredPrimarySources,
459
+ requiredSecondarySources,
460
+ requiredTargetCoverage,
461
+ requiredPrimaryTargetCoverage,
462
+ primarySourceCount: primary.length,
463
+ secondarySourceCount: secondary.length,
464
+ targets,
465
+ coveredTargets,
466
+ primaryCoveredTargets,
467
+ uncoveredTargets,
468
+ primaryUncoveredTargets,
469
+ missing,
470
+ nextActions,
471
+ reason: adequate
472
+ ? (intensity === 'none'
473
+ ? 'No research burden for this run.'
474
+ : 'Research evidence covers the requested targets.')
475
+ : clampResearchText(missing.join(' ') || 'Research evidence is still incomplete.', 320),
476
+ };
477
+ }
478
+
479
+ function formatResearchAdequacyGuidance(assessment = null) {
480
+ if (!assessment || assessment.adequate !== false) return '';
481
+ const lines = [
482
+ 'Research self-check: evidence is still incomplete for this run.',
483
+ assessment.reason ? `Gap: ${assessment.reason}` : '',
484
+ assessment.nextActions?.length
485
+ ? `Next safe steps:\n- ${assessment.nextActions.join('\n- ')}`
486
+ : '',
487
+ '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.',
488
+ ];
489
+ return lines.filter(Boolean).join('\n');
490
+ }
491
+
175
492
  function isSubstantiveProgressEvidence(item = {}) {
176
493
  if (!isSubstantiveProgressToolName(item.toolName)) return false;
177
494
  if (item.evidenceSource === 'messaging') return false;
@@ -246,12 +563,16 @@ function buildAutonomousRecoveryContext({ err, toolExecutions = [], tools = [],
246
563
  module.exports = {
247
564
  classifyToolExecution,
248
565
  deriveEvidenceSource,
566
+ assessResearchAdequacy,
567
+ formatResearchAdequacyGuidance,
249
568
  gatheredNewEvidence,
250
569
  isSubstantiveProgressEvidence,
251
570
  isSubstantiveProgressToolName,
571
+ resolveResearchIntensity,
252
572
  summarizeProgressToolExecutions,
253
573
  summarizeToolExecutions,
254
574
  summarizeAvailableTools,
255
575
  inferToolFailureMessage,
256
576
  buildAutonomousRecoveryContext,
577
+ resolveDeclaredToolAccess,
257
578
  };