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.
Files changed (78) hide show
  1. package/LICENSE +67 -80
  2. package/README.md +7 -1
  3. package/docs/licensing.md +44 -0
  4. package/flutter_app/lib/main.dart +1 -0
  5. package/flutter_app/lib/main_app_shell.dart +186 -40
  6. package/flutter_app/lib/main_chat.dart +542 -80
  7. package/flutter_app/lib/main_controller.dart +66 -5
  8. package/flutter_app/lib/main_install.dart +9 -7
  9. package/flutter_app/lib/main_models.dart +171 -22
  10. package/flutter_app/lib/main_operations.dart +0 -49
  11. package/flutter_app/lib/main_settings.dart +338 -0
  12. package/flutter_app/lib/src/backend_client.dart +19 -0
  13. package/flutter_app/lib/src/local_runtime_paths.dart +60 -0
  14. package/lib/manager.js +10 -2
  15. package/package.json +1 -1
  16. package/runtime/paths.js +70 -0
  17. package/server/db/database.js +2 -2
  18. package/server/http/routes.js +1 -0
  19. package/server/public/.last_build_id +1 -1
  20. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  21. package/server/public/flutter_bootstrap.js +1 -1
  22. package/server/public/main.dart.js +84940 -83737
  23. package/server/routes/behavior.js +80 -0
  24. package/server/routes/settings.js +4 -0
  25. package/server/services/agents/manager.js +1 -1
  26. package/server/services/ai/history.js +1 -1
  27. package/server/services/ai/loop/agent_engine_core.js +110 -17
  28. package/server/services/ai/loop/blank_recovery.js +5 -4
  29. package/server/services/ai/loop/completion_judge.js +226 -33
  30. package/server/services/ai/loop/conversation_loop.js +92 -34
  31. package/server/services/ai/loop/messaging_delivery.js +47 -0
  32. package/server/services/ai/loop/progress_classification.js +2 -0
  33. package/server/services/ai/loopPolicy.js +24 -2
  34. package/server/services/ai/messagingFallback.js +17 -17
  35. package/server/services/ai/model_failure_cache.js +7 -0
  36. package/server/services/ai/systemPrompt.js +31 -122
  37. package/server/services/ai/taskAnalysis.js +86 -12
  38. package/server/services/ai/toolEvidence.js +68 -224
  39. package/server/services/ai/tools.js +60 -19
  40. package/server/services/behavior/config.js +251 -0
  41. package/server/services/behavior/defaults.js +68 -0
  42. package/server/services/behavior/delivery.js +176 -0
  43. package/server/services/behavior/index.js +43 -0
  44. package/server/services/behavior/model_client.js +35 -0
  45. package/server/services/behavior/modules/agent_identity.js +37 -0
  46. package/server/services/behavior/modules/channel_style.js +28 -0
  47. package/server/services/behavior/modules/index.js +29 -0
  48. package/server/services/behavior/modules/norms.js +101 -0
  49. package/server/services/behavior/modules/persona.js +172 -0
  50. package/server/services/behavior/modules/persona_prompt.js +238 -0
  51. package/server/services/behavior/modules/social_memory.js +86 -0
  52. package/server/services/behavior/modules/social_observability.js +94 -0
  53. package/server/services/behavior/modules/social_signals.js +41 -0
  54. package/server/services/behavior/modules/theory_of_mind.js +118 -0
  55. package/server/services/behavior/modules/turn_taking.js +238 -0
  56. package/server/services/behavior/pipeline.js +341 -0
  57. package/server/services/behavior/registry.js +78 -0
  58. package/server/services/behavior/signals.js +107 -0
  59. package/server/services/behavior/state.js +99 -0
  60. package/server/services/behavior/system_prompt.js +75 -0
  61. package/server/services/memory/manager.js +14 -33
  62. package/server/services/messaging/access_policy.js +269 -74
  63. package/server/services/messaging/automation.js +158 -27
  64. package/server/services/messaging/discord.js +11 -1
  65. package/server/services/messaging/formatting_guides.js +5 -4
  66. package/server/services/messaging/http_platforms.js +73 -28
  67. package/server/services/messaging/inbound_queue.js +74 -13
  68. package/server/services/messaging/inbound_store.js +33 -0
  69. package/server/services/messaging/manager.js +57 -5
  70. package/server/services/messaging/telegram.js +10 -1
  71. package/server/services/messaging/whatsapp.js +11 -0
  72. package/server/services/social_reach/channels/social_video.js +1 -1
  73. package/server/services/social_video/captions.js +2 -2
  74. package/server/services/social_video/service.js +194 -29
  75. package/server/services/voice/message.js +1 -1
  76. package/server/services/voice/runtime.js +2 -2
  77. package/server/utils/logger.js +19 -0
  78. package/server/services/ai/terminal_reply.js +0 -57
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ const express = require('express');
4
+ const { requireAuth } = require('../middleware/auth');
5
+ const {
6
+ MODULE_IDS,
7
+ cloneDefaults,
8
+ getBehaviorConfig,
9
+ setBehaviorConfig,
10
+ resolveBehaviorConfig,
11
+ } = require('../services/behavior');
12
+ const {
13
+ getAgentIdFromRequest,
14
+ resolveAgentId,
15
+ } = require('../services/agents/manager');
16
+ const { invalidateSystemPromptCache } = require('../services/ai/systemPrompt');
17
+
18
+ const router = express.Router();
19
+ router.use(requireAuth);
20
+
21
+ function requestAgentId(req) {
22
+ return resolveAgentId(req.session.userId, getAgentIdFromRequest(req));
23
+ }
24
+
25
+ router.get('/', (req, res) => {
26
+ const userId = req.session.userId;
27
+ const agentId = requestAgentId(req);
28
+ res.json({
29
+ agentId,
30
+ modules: MODULE_IDS,
31
+ defaults: cloneDefaults(),
32
+ config: getBehaviorConfig(userId, agentId),
33
+ });
34
+ });
35
+
36
+ router.put('/', (req, res) => {
37
+ if (!req.body?.config || typeof req.body.config !== 'object' || Array.isArray(req.body.config)) {
38
+ return res.status(400).json({ error: 'config must be an object' });
39
+ }
40
+ const userId = req.session.userId;
41
+ const agentId = requestAgentId(req);
42
+ const config = setBehaviorConfig(userId, agentId, req.body.config);
43
+ invalidateSystemPromptCache(userId, agentId);
44
+ return res.json({ agentId, config });
45
+ });
46
+
47
+ router.get('/effective', (req, res) => {
48
+ const userId = req.session.userId;
49
+ const agentId = requestAgentId(req);
50
+ const platform = String(req.query.platform || '').trim();
51
+ const chatId = String(req.query.chatId || '').trim();
52
+ if (!platform || !chatId) {
53
+ return res.status(400).json({ error: 'platform and chatId are required' });
54
+ }
55
+ return res.json({
56
+ agentId,
57
+ config: resolveBehaviorConfig(userId, agentId, {
58
+ platform,
59
+ chatId,
60
+ isGroup: req.query.isGroup !== 'false',
61
+ }),
62
+ });
63
+ });
64
+
65
+ router.get('/diagnostics', (req, res) => {
66
+ const userId = req.session.userId;
67
+ const agentId = requestAgentId(req);
68
+ const platform = String(req.query.platform || '').trim();
69
+ const chatId = String(req.query.chatId || '').trim();
70
+ if (!platform || !chatId) {
71
+ return res.status(400).json({ error: 'platform and chatId are required' });
72
+ }
73
+ const pipeline = req.app?.locals?.behaviorPipeline;
74
+ if (!pipeline?.getDiagnostics) {
75
+ return res.status(503).json({ error: 'Behavior runtime is not initialized' });
76
+ }
77
+ return res.json(pipeline.getDiagnostics(userId, agentId, platform, chatId));
78
+ });
79
+
80
+ module.exports = router;
@@ -346,6 +346,10 @@ router.put('/', async (req, res) => {
346
346
  });
347
347
 
348
348
  tx(Object.entries(normalizedBody));
349
+ if (Object.prototype.hasOwnProperty.call(normalizedBody, 'assistant_behavior_notes')) {
350
+ const { invalidateSystemPromptCache } = require('../services/ai/systemPrompt');
351
+ invalidateSystemPromptCache(userId, agentId);
352
+ }
349
353
 
350
354
  if (Object.keys(normalizedBody).some((key) => VOICE_SETTING_KEYS.has(key))) {
351
355
  const manager = req.app?.locals?.messagingManager;
@@ -77,7 +77,7 @@ function ensureMainAgent(userId) {
77
77
  userId,
78
78
  MAIN_AGENT_SLUG,
79
79
  'Main',
80
- 'Default personal assistant and fallback agent.',
80
+ 'Default personal AI contact and fallback agent.',
81
81
  'Handle general requests. Delegate to specialist agents only when their responsibilities clearly match.',
82
82
  '',
83
83
  );
@@ -126,7 +126,7 @@ async function summarizeMessages(
126
126
  const prompt = [
127
127
  {
128
128
  role: 'system',
129
- content: 'Compress conversation context. Preserve user goals, constraints, preferences, decisions, promised follow-ups, recurring schedules, important facts, tool outcomes, and unresolved issues. Keep concrete details (names, dates, times, statuses) and avoid vague wording. Keep the same personality context. Output plain text only.'
129
+ content: 'Compress conversation context. Preserve user goals, constraints, preferences, decisions, promised follow-ups, recurring schedules, important facts, tool outcomes, unresolved issues, and any user-stated communication preferences. Keep concrete details (names, dates, times, statuses) and avoid vague wording. Output plain text only.'
130
130
  },
131
131
  {
132
132
  role: 'user',
@@ -20,6 +20,10 @@ const { summarizeCapabilityHealth } = require('../capabilityHealth');
20
20
  const { shouldAcceptTaskComplete } = require('../completion');
21
21
  const { shortenRunId, summarizeForLog } = require('../logFormat');
22
22
  const { getProviderForUser } = require('../provider_selector');
23
+ const {
24
+ recordModelFailure,
25
+ recordModelSuccess,
26
+ } = require('../model_failure_cache');
23
27
  const { runConversation } = require('./conversation_loop');
24
28
  const {
25
29
  TERMINAL_STATUSES,
@@ -33,6 +37,7 @@ const {
33
37
  buildChurnAssessmentPrompt,
34
38
  buildCompletionDecisionPrompt,
35
39
  enforceTerminalReplyDecision,
40
+ enforceChurnAssessment,
36
41
  normalizeChurnAssessment,
37
42
  normalizeCompletionDecision,
38
43
  resolveRunGoalContext,
@@ -85,7 +90,6 @@ const {
85
90
  normalizeOutgoingMessage,
86
91
  clampRunContext,
87
92
  } = require('../messagingFallback');
88
- const { isDeferredWorkReply } = require('../terminal_reply');
89
93
  const {
90
94
  assessResearchAdequacy,
91
95
  summarizeToolExecutions,
@@ -638,6 +642,82 @@ class AgentEngine {
638
642
  });
639
643
  }
640
644
 
645
+ async inferStructured({
646
+ userId,
647
+ agentId = null,
648
+ modelId = null,
649
+ purpose = 'fast',
650
+ system,
651
+ prompt,
652
+ maxTokens = 400,
653
+ fallback = {},
654
+ signal = null,
655
+ }) {
656
+ const { getFailureFallbackModelId } = require('./conversation_loop');
657
+ const configuredFallbackId = getAiSettings(userId, agentId).fallback_model_id;
658
+ const failedModelIds = new Set();
659
+ let requestedModelId = modelId;
660
+ let selected = null;
661
+ let result = null;
662
+
663
+ for (let attempt = 0; attempt < 3; attempt += 1) {
664
+ selected = await getProviderForUser(
665
+ userId,
666
+ prompt,
667
+ false,
668
+ requestedModelId,
669
+ {
670
+ agentId,
671
+ signal,
672
+ selectionHint: {
673
+ purpose: purpose === 'general' ? 'general' : 'fast',
674
+ costMode: purpose === 'fast' ? 'economy' : 'balanced_auto',
675
+ },
676
+ },
677
+ );
678
+ try {
679
+ result = await this.requestStructuredJson({
680
+ provider: selected.provider,
681
+ providerName: selected.providerName,
682
+ model: selected.model,
683
+ messages: system ? [{ role: 'system', content: String(system) }] : [],
684
+ prompt: String(prompt || ''),
685
+ maxTokens,
686
+ normalize: (value) => value,
687
+ fallback,
688
+ telemetry: { userId, agentId, signal },
689
+ phase: 'behavior_inference',
690
+ });
691
+ recordModelSuccess(userId, agentId, selected.modelSelectionId);
692
+ break;
693
+ } catch (error) {
694
+ if (isAbortError(error, signal) || signal?.aborted) throw error;
695
+ recordModelFailure(userId, agentId, selected.modelSelectionId, error);
696
+ failedModelIds.add(selected.modelSelectionId);
697
+ if (attempt >= 2) throw error;
698
+ requestedModelId = await getFailureFallbackModelId(
699
+ userId,
700
+ agentId,
701
+ selected.modelSelectionId,
702
+ configuredFallbackId,
703
+ error,
704
+ signal,
705
+ failedModelIds,
706
+ );
707
+ if (!requestedModelId) throw error;
708
+ }
709
+ }
710
+
711
+ return {
712
+ parsed: result.value,
713
+ raw: result.raw,
714
+ usage: result.usage,
715
+ model: selected.model,
716
+ modelSelectionId: selected.modelSelectionId,
717
+ providerName: selected.providerName,
718
+ };
719
+ }
720
+
641
721
  async requestModelResponse({
642
722
  provider,
643
723
  providerName,
@@ -933,9 +1013,20 @@ class AgentEngine {
933
1013
  telemetry: options,
934
1014
  phase: 'churn_assessment',
935
1015
  });
1016
+ const researchAdequacy = assessResearchAdequacy({
1017
+ analysis,
1018
+ goalContext,
1019
+ toolExecutions,
1020
+ });
936
1021
  return {
937
- assessment: response.value,
1022
+ assessment: enforceChurnAssessment(response.value, {
1023
+ analysis,
1024
+ goalContext,
1025
+ toolExecutions,
1026
+ researchAdequacy,
1027
+ }),
938
1028
  usage: response.usage,
1029
+ researchAdequacy,
939
1030
  };
940
1031
  }
941
1032
 
@@ -962,6 +1053,15 @@ class AgentEngine {
962
1053
  content: [
963
1054
  'Return JSON only. Distill the current thread working state. Keep it concise and concrete.',
964
1055
  'Track summary, open_commitments, unresolved_questions, referenced_entities, and last_verified_facts. Do not invent facts.',
1056
+ options.memoryScope?.scopeType === 'channel'
1057
+ ? [
1058
+ 'This is a shared room. Extract only durable room or participant facts that are safe and useful in this room.',
1059
+ 'Do not infer or store facts about the authenticated owner from another participant’s message.',
1060
+ options.context?.socialIntelligence?.message?.sender
1061
+ ? `For facts about the current speaker, use the canonical subject "${options.source}:${options.context.socialIntelligence.message.sender}".`
1062
+ : '',
1063
+ ].filter(Boolean).join(' ')
1064
+ : '',
965
1065
  buildMemoryConsolidationInstructions(new Date().toISOString()),
966
1066
  'Schema:',
967
1067
  JSON.stringify({
@@ -1038,6 +1138,14 @@ class AgentEngine {
1038
1138
  agentId: options.agentId || null,
1039
1139
  conversationId,
1040
1140
  runId,
1141
+ scope: options.memoryScope || undefined,
1142
+ metadata: options.memoryScope
1143
+ ? {
1144
+ trustLevel: 'shared_room_conversation',
1145
+ platform: options.source || null,
1146
+ chatId: options.chatId || null,
1147
+ }
1148
+ : undefined,
1041
1149
  signal: options.signal,
1042
1150
  },
1043
1151
  );
@@ -1234,21 +1342,6 @@ class AgentEngine {
1234
1342
  return options.reasoningEffort || process.env.REASONING_EFFORT || 'low';
1235
1343
  }
1236
1344
 
1237
- shouldFastCompleteVoiceReply({
1238
- options = {},
1239
- toolExecutions = [],
1240
- failedStepCount = 0,
1241
- messagingSent = false,
1242
- lastReply = '',
1243
- }) {
1244
- return options.latencyProfile === 'voice'
1245
- && toolExecutions.length === 0
1246
- && failedStepCount === 0
1247
- && !messagingSent
1248
- && Boolean(String(lastReply || '').trim())
1249
- && !isDeferredWorkReply(lastReply);
1250
- }
1251
-
1252
1345
  getMessagingRetryLimit(maxIterations) {
1253
1346
  // Cap at 3: more than 3 autonomous messaging retries indicates a structural
1254
1347
  // problem (model unavailable, bad config) that more retries won't solve.
@@ -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