neoagent 2.4.1-beta.19 → 2.4.1-beta.21

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 (55) hide show
  1. package/README.md +4 -1
  2. package/docs/getting-started.md +9 -3
  3. package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
  4. package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
  5. package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
  6. package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
  7. package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
  8. package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
  9. package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
  10. package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
  11. package/flutter_app/lib/features/location/location_service.dart +3 -0
  12. package/flutter_app/lib/main.dart +1 -0
  13. package/flutter_app/lib/main_account_settings.dart +9 -33
  14. package/flutter_app/lib/main_app_shell.dart +237 -197
  15. package/flutter_app/lib/main_controller.dart +0 -25
  16. package/flutter_app/lib/main_devices.dart +2 -0
  17. package/flutter_app/lib/main_models.dart +144 -0
  18. package/flutter_app/lib/main_operations.dart +150 -19
  19. package/flutter_app/lib/main_shared.dart +642 -195
  20. package/flutter_app/lib/main_theme.dart +2 -0
  21. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +3 -1
  22. package/flutter_app/lib/src/security/password_strength.dart +84 -0
  23. package/flutter_app/lib/src/theme/palette.dart +15 -15
  24. package/flutter_app/pubspec.yaml +3 -0
  25. package/flutter_app/web/favicon_light.svg +3 -0
  26. package/flutter_app/web/icons/Icon-192-light.png +0 -0
  27. package/flutter_app/web/icons/Icon-512-light.png +0 -0
  28. package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
  29. package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
  30. package/lib/manager.js +282 -81
  31. package/package.json +17 -3
  32. package/server/config/origins.js +3 -1
  33. package/server/db/database.js +73 -0
  34. package/server/public/.last_build_id +1 -1
  35. package/server/public/assets/AssetManifest.bin +1 -1
  36. package/server/public/assets/AssetManifest.bin.json +1 -1
  37. package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
  38. package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
  39. package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
  40. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  41. package/server/public/favicon_light.svg +3 -0
  42. package/server/public/flutter_bootstrap.js +1 -1
  43. package/server/public/icons/Icon-192-light.png +0 -0
  44. package/server/public/icons/Icon-512-light.png +0 -0
  45. package/server/public/icons/Icon-maskable-192-light.png +0 -0
  46. package/server/public/icons/Icon-maskable-512-light.png +0 -0
  47. package/server/public/main.dart.js +68769 -68268
  48. package/server/routes/agent_profiles.js +3 -0
  49. package/server/routes/memory.js +22 -1
  50. package/server/services/account/password_policy.js +6 -1
  51. package/server/services/memory/intelligence.js +181 -0
  52. package/server/services/memory/manager.js +475 -25
  53. package/server/utils/security.js +3 -0
  54. package/server/services/memory/openhuman_uplift.test.js +0 -98
  55. package/server/utils/version.test.js +0 -39
@@ -41,6 +41,7 @@ router.put('/:id', (req, res) => {
41
41
  try {
42
42
  res.json(updateAgent(req.session.userId, req.params.id, req.body || {}));
43
43
  } catch (err) {
44
+ if (err.message === 'Agent not found.') return res.status(404).json({ error: err.message });
44
45
  res.status(400).json({ error: sanitizeError(err) });
45
46
  }
46
47
  });
@@ -49,6 +50,7 @@ router.post('/:id/default', (req, res) => {
49
50
  try {
50
51
  res.json(setDefaultAgent(req.session.userId, req.params.id));
51
52
  } catch (err) {
53
+ if (err.message === 'Agent not found.') return res.status(404).json({ error: err.message });
52
54
  res.status(400).json({ error: sanitizeError(err) });
53
55
  }
54
56
  });
@@ -57,6 +59,7 @@ router.delete('/:id', (req, res) => {
57
59
  try {
58
60
  res.json(archiveAgent(req.session.userId, req.params.id));
59
61
  } catch (err) {
62
+ if (err.message === 'Agent not found.') return res.status(404).json({ error: err.message });
60
63
  res.status(400).json({ error: sanitizeError(err) });
61
64
  }
62
65
  });
@@ -111,13 +111,19 @@ router.get('/', (req, res) => {
111
111
  const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
112
112
  const coreMemory = { ...(mm.getCoreMemory(userId, { agentId }) || {}) };
113
113
  delete coreMemory.active_context;
114
+ const knowledgeViews = mm.listKnowledgeViews(userId, { agentId, limit: 12 });
114
115
  res.json({
115
116
  agentId,
116
117
  assistantBehaviorNotes: mm.getAssistantBehaviorNotes(userId, { agentId }),
117
118
  assistantSelfState: mm.getAssistantSelfState(userId, { agentId }),
118
119
  dailyLogs: mm.listDailyLogs(7, userId),
119
120
  apiKeys: Object.keys(mm.readApiKeys(userId)),
120
- coreMemory
121
+ coreMemory,
122
+ stats: mm.getMemoryStats(userId, { agentId }),
123
+ entities: mm.listEntities(userId, { agentId, limit: 12 }),
124
+ knowledgeViews,
125
+ ingestionOverview: mm.getIngestionOverview(userId, { agentId }),
126
+ recentKnowledgeChanges: mm.listRecentKnowledgeChanges(userId, { agentId, limit: 8 }),
121
127
  });
122
128
  });
123
129
 
@@ -204,6 +210,21 @@ router.get('/knowledge-views', (req, res) => {
204
210
  }
205
211
  });
206
212
 
213
+ router.get('/entities', (req, res) => {
214
+ const mm = req.app.locals.memoryManager;
215
+ const userId = req.session.userId;
216
+ const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
217
+ try {
218
+ res.json(mm.listEntities(userId, {
219
+ agentId,
220
+ query: req.query.query || null,
221
+ limit: req.query.limit,
222
+ }));
223
+ } catch (err) {
224
+ res.status(500).json({ error: sanitizeError(err) });
225
+ }
226
+ });
227
+
207
228
  router.post('/knowledge-views/materialize', (req, res) => {
208
229
  const mm = req.app.locals.memoryManager;
209
230
  const userId = req.session.userId;
@@ -113,7 +113,12 @@ function evaluatePasswordStrength(password, context = {}) {
113
113
  label,
114
114
  length,
115
115
  hasMinimumLength: length >= MIN_PASSWORD_LENGTH,
116
- isAcceptable: length >= MIN_PASSWORD_LENGTH && score >= MIN_PASSWORD_SCORE,
116
+ isAcceptable: length >= MIN_PASSWORD_LENGTH
117
+ && score >= MIN_PASSWORD_SCORE
118
+ && !containsPersonalInfo
119
+ && !usesCommonPattern
120
+ && !sequential
121
+ && !repeatedRuns,
117
122
  feedback,
118
123
  };
119
124
  }
@@ -0,0 +1,181 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ const ENTITY_KIND_PATTERNS = [
6
+ ['email', /^[^\s@]+@[^\s@]+\.[^\s@]+$/],
7
+ ['url', /^https?:\/\//i],
8
+ ['version', /^v?\d+(?:\.\d+){1,3}$/i],
9
+ ['identifier', /^[a-z0-9][a-z0-9_-]{2,}$/i],
10
+ ];
11
+
12
+ function clamp(value, min, max, fallback) {
13
+ const number = Number(value);
14
+ return Number.isFinite(number) ? Math.max(min, Math.min(max, number)) : fallback;
15
+ }
16
+
17
+ function stableHash(value) {
18
+ return crypto
19
+ .createHash('sha256')
20
+ .update(String(value || '').trim().toLowerCase())
21
+ .digest('hex');
22
+ }
23
+
24
+ function canonicalEntityKey(name) {
25
+ return String(name || '')
26
+ .trim()
27
+ .toLowerCase()
28
+ .replace(/[^\p{L}\p{N}._@:/+-]+/gu, ' ')
29
+ .replace(/\s+/g, ' ')
30
+ .slice(0, 160);
31
+ }
32
+
33
+ function classifyEntity(name) {
34
+ const value = String(name || '').trim();
35
+ for (const [kind, pattern] of ENTITY_KIND_PATTERNS) {
36
+ if (pattern.test(value)) return kind;
37
+ }
38
+ if (/^[A-Z][A-Z0-9_-]{2,}$/.test(value)) return 'acronym';
39
+ if (/\.(js|ts|dart|py|json|md|yaml|yml|sql|rs|go|java|kt|swift|css|html)$/i.test(value)) {
40
+ return 'file';
41
+ }
42
+ return 'concept';
43
+ }
44
+
45
+ function uniqueByKey(items, getKey) {
46
+ const seen = new Set();
47
+ const result = [];
48
+ for (const item of items) {
49
+ const key = getKey(item);
50
+ if (!key || seen.has(key)) continue;
51
+ seen.add(key);
52
+ result.push(item);
53
+ }
54
+ return result;
55
+ }
56
+
57
+ function extractEntities(text, { maxEntities = 16 } = {}) {
58
+ const raw = String(text || '');
59
+ const candidates = [];
60
+
61
+ for (const match of raw.matchAll(/[^\s@]+@[^\s@]+\.[^\s@]+/g)) {
62
+ candidates.push(match[0]);
63
+ }
64
+ for (const match of raw.matchAll(/https?:\/\/[^\s)]+/gi)) {
65
+ candidates.push(match[0].replace(/[.,;]+$/g, ''));
66
+ }
67
+ for (const match of raw.matchAll(/\b[A-Z][\p{L}\p{N}_+./:-]*(?:\s+[A-Z][\p{L}\p{N}_+./:-]*){0,4}\b/gu)) {
68
+ const value = match[0].trim();
69
+ if (value.length > 2) candidates.push(value);
70
+ }
71
+ for (const match of raw.matchAll(/\b[\p{L}\p{N}_./-]+\.(?:js|ts|dart|py|json|md|yaml|yml|sql|rs|go|java|kt|swift|css|html)\b/giu)) {
72
+ candidates.push(match[0]);
73
+ }
74
+
75
+ return uniqueByKey(
76
+ candidates
77
+ .map((name) => {
78
+ const normalized = String(name || '').trim().replace(/\s+/g, ' ');
79
+ const key = canonicalEntityKey(normalized);
80
+ if (!key || key.length < 2) return null;
81
+ return {
82
+ key,
83
+ name: normalized.slice(0, 160),
84
+ kind: classifyEntity(normalized),
85
+ };
86
+ })
87
+ .filter(Boolean),
88
+ (entity) => entity.key,
89
+ ).slice(0, maxEntities);
90
+ }
91
+
92
+ function extractKeywords(text, { maxKeywords = 24 } = {}) {
93
+ const counts = new Map();
94
+ const tokens = String(text || '')
95
+ .toLowerCase()
96
+ .match(/[\p{L}\p{N}_-]{5,}/gu) || [];
97
+ for (const token of tokens) {
98
+ if (/^\d+$/.test(token)) continue;
99
+ counts.set(token, (counts.get(token) || 0) + 1);
100
+ }
101
+ return [...counts.entries()]
102
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
103
+ .slice(0, maxKeywords)
104
+ .map(([keyword]) => keyword);
105
+ }
106
+
107
+ function splitSentences(text) {
108
+ return String(text || '')
109
+ .split(/(?<=[.!?])\s+|\n+/u)
110
+ .map((part) => part.trim())
111
+ .filter(Boolean);
112
+ }
113
+
114
+ function buildFacts({ content, category, sourceRef, metadata } = {}) {
115
+ const sentences = splitSentences(content);
116
+ const entities = extractEntities(content);
117
+ const keywords = extractKeywords(content, { maxKeywords: 10 });
118
+ const subject = entities[0]?.name || String(category || 'memory').replace(/_/g, ' ');
119
+ const predicate = String(category || 'episodic').replace(/_/g, ' ');
120
+ const sourceType = sourceRef?.sourceType || metadata?.sourceType || null;
121
+
122
+ const factTexts = sentences.length
123
+ ? sentences.slice(0, 6)
124
+ : [String(content || '').trim()].filter(Boolean);
125
+
126
+ return factTexts.map((text, index) => ({
127
+ subject: String(subject || 'memory').slice(0, 180),
128
+ predicate: index === 0 ? predicate : 'detail',
129
+ object: text.slice(0, 900),
130
+ category,
131
+ confidence: sourceType === 'llm_import' ? 0.74 : 0.68,
132
+ metadata: {
133
+ keywords,
134
+ sourceType,
135
+ extractedBy: 'local_memory_intelligence',
136
+ },
137
+ }));
138
+ }
139
+
140
+ function summarizeForPrompt(memory) {
141
+ const entities = Array.isArray(memory.entities) ? memory.entities : [];
142
+ const content = String(memory.content || '').replace(/\s+/g, ' ').trim();
143
+ const entitySuffix = entities.length
144
+ ? ` (${entities.slice(0, 4).map((entity) => entity.name || entity).join(', ')})`
145
+ : '';
146
+ return `${content}${entitySuffix}`.slice(0, 900);
147
+ }
148
+
149
+ function rankFuse(rank, weight = 1) {
150
+ if (!Number.isFinite(rank) || rank < 0) return 0;
151
+ return weight / (60 + rank + 1);
152
+ }
153
+
154
+ function scoreMemoryCandidate({
155
+ semanticRank = -1,
156
+ lexicalRank = -1,
157
+ entityRank = -1,
158
+ baseScore = 0,
159
+ importance = 5,
160
+ accessCount = 0,
161
+ freshness = 1,
162
+ } = {}) {
163
+ const fused = (
164
+ rankFuse(semanticRank, 1.0) +
165
+ rankFuse(lexicalRank, 0.85) +
166
+ rankFuse(entityRank, 0.95)
167
+ ) * 20;
168
+ const quality = 0.15 + clamp(importance, 1, 10, 5) / 22;
169
+ const usage = Math.min(0.08, Math.log1p(Math.max(0, Number(accessCount) || 0)) / 50);
170
+ return Math.max(baseScore, fused + quality + usage) * freshness;
171
+ }
172
+
173
+ module.exports = {
174
+ buildFacts,
175
+ canonicalEntityKey,
176
+ extractEntities,
177
+ extractKeywords,
178
+ scoreMemoryCandidate,
179
+ stableHash,
180
+ summarizeForPrompt,
181
+ };