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
@@ -33,9 +33,9 @@ function shouldContinueAfterRecoverableToolFailure({
33
33
  if (Number(remainingIterations || 0) <= 0) return false;
34
34
  const failedExecution = latestFailedToolExecution(toolExecutions);
35
35
  if (!isRecoverableInternalToolFailure(failedExecution)) return false;
36
- const visible = normalizeOutgoingMessage(lastContent || '');
37
- if (!visible) return false;
38
- return /\b(blocked|could not|cannot|can't|do not have a confirmed finished result|not found|missing|internal tool issue)\b/i.test(visible);
36
+ // Structural only: recoverable internal tool failure + remaining budget.
37
+ // Do not phrase-match the draft reply.
38
+ return Boolean(normalizeOutgoingMessage(lastContent || '') || failedExecution);
39
39
  }
40
40
 
41
41
  function buildBlankAfterToolFailureGuidance(toolExecutions = []) {
@@ -46,7 +46,8 @@ function buildBlankAfterToolFailureGuidance(toolExecutions = []) {
46
46
  return [
47
47
  `The previous tool "${toolName}" failed with: ${summarizeForLog(failure, 240)}.`,
48
48
  'The latest assistant turn returned no user-facing answer and no tool call, so the task is not terminal.',
49
- 'Continue with the next safe recovery action: retry with corrected arguments, use another available tool, verify from existing evidence, or report a real blocker only if no autonomous path remains.',
49
+ 'Continue with the next safe recovery action now in this same turn: retry with corrected arguments, use another available tool, verify from existing evidence, or report a real blocker only if no autonomous path remains.',
50
+ 'Do not invent a finished result. Prefer a concrete recovery step or a truthful partial answer over silence.',
50
51
  ].join(' ');
51
52
  }
52
53
 
@@ -65,6 +65,7 @@ async function publishInterimUpdate(engine, {
65
65
  persistConversation: true,
66
66
  metadata,
67
67
  deliveryKind: 'interim',
68
+ signal: runMeta.abortController?.signal || null,
68
69
  });
69
70
  requireSuccessfulMessagingDelivery(deliveryResult, 'Interim messaging delivery');
70
71
  } else if (triggerSource === 'voice_live') {
@@ -3,6 +3,8 @@
3
3
  const { normalizeCompletionConfidence } = require('../completion');
4
4
  const { normalizeOutgoingMessage } = require('../messagingFallback');
5
5
  const {
6
+ assessResearchAdequacy,
7
+ formatResearchAdequacyGuidance,
6
8
  summarizeAvailableTools,
7
9
  summarizeToolExecutions,
8
10
  } = require('../toolEvidence');
@@ -194,8 +196,15 @@ function buildCompletionDecisionPrompt({
194
196
  lastReply,
195
197
  iteration,
196
198
  maxIterations,
199
+ analysis = null,
200
+ researchAdequacy = null,
197
201
  }) {
198
202
  const draftReply = normalizeOutgoingMessage(lastReply) || '';
203
+ const adequacy = researchAdequacy || assessResearchAdequacy({
204
+ analysis,
205
+ goalContext,
206
+ toolExecutions,
207
+ });
199
208
  const lines = [
200
209
  'Return JSON only.',
201
210
  'Decide whether this run should continue autonomously or stop now.',
@@ -210,13 +219,33 @@ function buildCompletionDecisionPrompt({
210
219
  '- A tool-specific API error, timeout, rate limit, or missing result inside this run is usually "continue", not "blocked", if any other available tool could still make progress.',
211
220
  '- Repeated read-only inspection that has already established the relevant object is absent or unchanged is not progress. Accept a concise complete/blocker reply instead of requiring more searching.',
212
221
  `- If completion_confidence_required is ${goalContext.effectiveCompletionConfidence} and the latest draft depends on unverified assumptions, use "continue" so the run can gather evidence, inspect state, or narrow the reply.`,
213
- triggerSource === 'messaging' && messagingSent
214
- ? '- A final reply was already delivered via send_message. Use "complete" unless concrete task work remains.'
215
- : triggerSource === 'messaging'
216
- ? '- For messaging, do not stop on a partial status message. Continue unless the task is actually complete or externally blocked.'
217
- : '- Do not stop just because you wrote a status update. Continue unless the task is actually complete or externally blocked.',
222
+ '- When research intensity is light or deep, use "continue" until the required primary/source coverage and target coverage are met, or the draft is an explicit blocker naming exactly what could not be verified.',
223
+ '- Search snippets, memory, and model priors are leads, not completion evidence. Prefer opened/fetched primary sources before "complete".',
224
+ '- If the latest draft invents entities, products, people, files, results, or actions that are not supported by tool evidence, use "continue" so the run can gather evidence or rewrite into a truthful partial/blocker answer.',
225
+ '- A polished-sounding answer is not complete if key requested targets still lack direct evidence.',
226
+ '- If the latest draft only announces unfinished work, promises a future update, or asks the user to wait without a concrete result or blocker, use "continue" so the run keeps acting.',
227
+ '- If the latest draft asks for missing required user input, confirmation, or a choice needed to proceed, use "blocked" so the run waits instead of repeating the same ask.',
218
228
  ];
219
229
 
230
+ if (adequacy.intensity !== 'none') {
231
+ lines.push(
232
+ `- Research intensity for this run is ${adequacy.intensity}. Current coverage: primary=${adequacy.primarySourceCount}/${adequacy.requiredPrimarySources}, secondary=${adequacy.secondarySourceCount}, targets_covered=${adequacy.coveredTargets.length}/${Math.max(adequacy.requiredTargetCoverage, adequacy.targets.length)}.`,
233
+ );
234
+ }
235
+ if (adequacy.adequate === false) {
236
+ lines.push(
237
+ `- Research is still incomplete (${adequacy.reason}). Use "continue" unless the latest draft is an explicit blocker naming the exact missing evidence.`,
238
+ );
239
+ }
240
+
241
+ if (triggerSource === 'messaging' && messagingSent) {
242
+ lines.push('- A final reply was already delivered via send_message. Use "complete" unless concrete task work remains.');
243
+ } else if (triggerSource === 'messaging') {
244
+ lines.push('- For messaging, do not stop on a partial status message. Continue unless the task is actually complete or externally blocked.');
245
+ } else {
246
+ lines.push('- Do not stop just because you wrote a status update. Continue unless the task is actually complete or externally blocked.');
247
+ }
248
+
220
249
  lines.push(
221
250
  goalContext.effectiveGoal ? `Goal: ${goalContext.effectiveGoal}` : '',
222
251
  goalContext.persistedGoalPrompt,
@@ -227,6 +256,16 @@ function buildCompletionDecisionPrompt({
227
256
  `Current iteration: ${iteration} of ${maxIterations}.`,
228
257
  `Available tools in this run: ${summarizeAvailableTools(tools) || 'none'}`,
229
258
  `Recent tool evidence:\n${summarizeToolExecutions(toolExecutions, 8) || 'none'}`,
259
+ adequacy.intensity !== 'none'
260
+ ? `Research adequacy: intensity=${adequacy.intensity}; adequate=${adequacy.adequate}; reason=${adequacy.reason}`
261
+ : '',
262
+ adequacy.targets.length
263
+ ? `Research targets: ${adequacy.targets.join('; ')}`
264
+ : '',
265
+ adequacy.uncoveredTargets.length
266
+ ? `Uncovered research targets: ${adequacy.uncoveredTargets.join('; ')}`
267
+ : '',
268
+ formatResearchAdequacyGuidance(adequacy),
230
269
  `Latest draft reply:\n${draftReply || '(empty)'}`,
231
270
  );
232
271
  return lines.filter(Boolean).join('\n');
@@ -241,6 +280,39 @@ function normalizeCompletionDecision(raw, fallbackStatus = 'continue') {
241
280
  };
242
281
  }
243
282
 
283
+ function enforceTerminalReplyDecision(decision, lastReply, options = {}) {
284
+ // Natural-language terminality is judged by the model completion decision.
285
+ // Runtime only enforces structural evidence contracts (research adequacy).
286
+ const researchAdequacy = options.researchAdequacy
287
+ || assessResearchAdequacy({
288
+ analysis: options.analysis || null,
289
+ goalContext: options.goalContext || null,
290
+ toolExecutions: options.toolExecutions || [],
291
+ });
292
+ if (
293
+ researchAdequacy
294
+ && researchAdequacy.adequate === false
295
+ && (decision?.status === 'complete' || decision?.status === 'blocked')
296
+ ) {
297
+ // Allow true blocked outcomes only when the model already chose blocked AND
298
+ // research cannot progress further (no remaining targets/primary gap that
299
+ // tools could still cover). Otherwise force continue for evidence gathering.
300
+ if (
301
+ decision?.status === 'blocked'
302
+ && researchAdequacy.uncoveredTargets.length === 0
303
+ && researchAdequacy.primarySourceCount >= researchAdequacy.requiredPrimarySources
304
+ ) {
305
+ return decision;
306
+ }
307
+ return {
308
+ status: 'continue',
309
+ reason: researchAdequacy.reason
310
+ || 'Research evidence is still incomplete for the requested targets; continue gathering sources before finishing.',
311
+ };
312
+ }
313
+ return decision;
314
+ }
315
+
244
316
  // Intentionally lightweight (200-token cap, self-contained) so the model can
245
317
  // answer cold without re-reading full conversation history.
246
318
  function buildChurnAssessmentPrompt({
@@ -249,7 +321,14 @@ function buildChurnAssessmentPrompt({
249
321
  goalContext,
250
322
  toolExecutions,
251
323
  iteration,
324
+ analysis = null,
325
+ researchAdequacy = null,
252
326
  }) {
327
+ const adequacy = researchAdequacy || assessResearchAdequacy({
328
+ analysis,
329
+ goalContext,
330
+ toolExecutions,
331
+ });
253
332
  const lines = [
254
333
  'Return JSON only.',
255
334
  'Self-assess your current loop state — are you making genuine progress or spinning?',
@@ -263,15 +342,50 @@ function buildChurnAssessmentPrompt({
263
342
  : '',
264
343
  `Iteration: ${iteration}`,
265
344
  `Recent tool evidence:\n${summarizeToolExecutions(toolExecutions, 6) || 'none'}`,
345
+ adequacy.intensity !== 'none'
346
+ ? `Research adequacy: intensity=${adequacy.intensity}; adequate=${adequacy.adequate}; covered=${adequacy.coveredTargets.length}/${Math.max(adequacy.requiredTargetCoverage, adequacy.targets.length)}; primary=${adequacy.primarySourceCount}/${adequacy.requiredPrimarySources}.`
347
+ : '',
348
+ adequacy.uncoveredTargets.length
349
+ ? `Still uncovered research targets: ${adequacy.uncoveredTargets.join('; ')}.`
350
+ : '',
266
351
  '',
267
352
  'Assessment rules:',
268
353
  '- "progressing": You are systematically gathering necessary context and the next concrete action is already determined — you know exactly what to do next.',
269
354
  '- "churn": You are re-reading/re-searching information already in context, or exploring without a clear next concrete step. Accept the nudge and act.',
270
355
  '- "blocked": No concrete action is available in this run. You have all the evidence needed to deliver a truthful final answer or a specific external blocker.',
356
+ '- For multi-target research, keep "progressing" while uncovered targets remain and a fresh primary source can still be opened. Do not mark "blocked" just because you have partial notes.',
357
+ '- Re-querying the same snippet source for an already covered target is "churn". Opening a different primary source for an uncovered target is "progressing".',
358
+ adequacy.adequate === false
359
+ ? '- Research adequacy is currently incomplete. Prefer "progressing" if a new primary source for an uncovered target is still available; use "churn" only for repeated identical reads; do not use "blocked" unless tools cannot reach remaining targets.'
360
+ : '',
271
361
  ];
272
362
  return lines.filter(Boolean).join('\n');
273
363
  }
274
364
 
365
+ function enforceChurnAssessment(assessment, options = {}) {
366
+ const normalized = normalizeChurnAssessment(assessment);
367
+ const researchAdequacy = options.researchAdequacy
368
+ || assessResearchAdequacy({
369
+ analysis: options.analysis || null,
370
+ goalContext: options.goalContext || null,
371
+ toolExecutions: options.toolExecutions || [],
372
+ });
373
+ if (
374
+ researchAdequacy
375
+ && researchAdequacy.adequate === false
376
+ && researchAdequacy.intensity !== 'none'
377
+ && normalized.assessment === 'blocked'
378
+ && (researchAdequacy.uncoveredTargets?.length > 0 || researchAdequacy.primarySourceCount < researchAdequacy.requiredPrimarySources)
379
+ ) {
380
+ return {
381
+ assessment: 'progressing',
382
+ reason: researchAdequacy.reason
383
+ || 'Research targets remain uncovered; keep gathering primary sources instead of force-finishing.',
384
+ };
385
+ }
386
+ return normalized;
387
+ }
388
+
275
389
  function normalizeChurnAssessment(raw) {
276
390
  const allowed = new Set(['progressing', 'churn', 'blocked']);
277
391
  const assessment = String(raw?.assessment || '').trim().toLowerCase();
@@ -285,9 +399,11 @@ module.exports = {
285
399
  buildChurnAssessmentPrompt,
286
400
  buildCompletionDecisionPrompt,
287
401
  buildGoalContractPrompt,
402
+ enforceTerminalReplyDecision,
288
403
  goalContractFromAnalysis,
289
404
  goalContractFromPlan,
290
405
  mergeGoalContracts,
406
+ enforceChurnAssessment,
291
407
  normalizeChurnAssessment,
292
408
  normalizeCompletionDecision,
293
409
  normalizeGoalContract,