neoagent 2.4.1-beta.9 → 2.4.1

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 (234) hide show
  1. package/.env.example +33 -3
  2. package/LICENSE +111 -56
  3. package/README.md +8 -3
  4. package/docs/configuration.md +8 -0
  5. package/docs/getting-started.md +9 -3
  6. package/docs/index.md +4 -0
  7. package/extensions/chrome-browser/background.mjs +36 -4
  8. package/extensions/chrome-browser/icons/icon128.png +0 -0
  9. package/extensions/chrome-browser/icons/icon16.png +0 -0
  10. package/extensions/chrome-browser/icons/icon48.png +0 -0
  11. package/extensions/chrome-browser/icons/logo.svg +39 -8
  12. package/extensions/chrome-browser/manifest.json +3 -3
  13. package/extensions/chrome-browser/popup.html +5 -1
  14. package/extensions/chrome-browser/popup.js +13 -0
  15. package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
  16. package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  17. package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  18. package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  19. package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  20. package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  21. package/flutter_app/assets/branding/app_icon_1024.png +0 -0
  22. package/flutter_app/assets/branding/app_icon_128.png +0 -0
  23. package/flutter_app/assets/branding/app_icon_192.png +0 -0
  24. package/flutter_app/assets/branding/app_icon_256.png +0 -0
  25. package/flutter_app/assets/branding/app_icon_32.png +0 -0
  26. package/flutter_app/assets/branding/app_icon_512.png +0 -0
  27. package/flutter_app/assets/branding/app_icon_64.png +0 -0
  28. package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
  29. package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
  30. package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
  31. package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
  32. package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
  33. package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
  34. package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
  35. package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
  36. package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
  37. package/flutter_app/assets/branding/tray_icon_template.png +0 -0
  38. package/flutter_app/lib/features/location/location_service.dart +3 -0
  39. package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +391 -382
  40. package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +743 -0
  41. package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +18 -16
  42. package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +19 -18
  43. package/flutter_app/lib/features/onboarding/onboarding_shell.dart +8 -1
  44. package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +16 -13
  45. package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +17 -13
  46. package/flutter_app/lib/main.dart +2 -0
  47. package/flutter_app/lib/main_account_settings.dart +10 -34
  48. package/flutter_app/lib/main_admin.dart +14 -1
  49. package/flutter_app/lib/main_app_shell.dart +377 -340
  50. package/flutter_app/lib/main_chat.dart +707 -227
  51. package/flutter_app/lib/main_controller.dart +197 -42
  52. package/flutter_app/lib/main_devices.dart +436 -4
  53. package/flutter_app/lib/main_integrations.dart +255 -6
  54. package/flutter_app/lib/main_launcher.dart +1 -1
  55. package/flutter_app/lib/main_model_picker.dart +685 -0
  56. package/flutter_app/lib/main_models.dart +212 -0
  57. package/flutter_app/lib/main_navigation.dart +1 -9
  58. package/flutter_app/lib/main_operations.dart +1417 -614
  59. package/flutter_app/lib/main_settings.dart +673 -871
  60. package/flutter_app/lib/main_shared.dart +971 -443
  61. package/flutter_app/lib/main_spacing.dart +4 -4
  62. package/flutter_app/lib/main_theme.dart +22 -14
  63. package/flutter_app/lib/main_unified.dart +5 -72
  64. package/flutter_app/lib/src/android_apk_drop_zone.dart +24 -0
  65. package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +13 -0
  66. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +219 -0
  67. package/flutter_app/lib/src/backend_client.dart +46 -0
  68. package/flutter_app/lib/src/desktop_companion_actions.dart +30 -7
  69. package/flutter_app/lib/src/desktop_companion_io.dart +71 -1
  70. package/flutter_app/lib/src/security/password_strength.dart +84 -0
  71. package/flutter_app/lib/src/stream_renderer.dart +42 -3
  72. package/flutter_app/lib/src/theme/palette.dart +76 -34
  73. package/flutter_app/linux/runner/resources/app_icon.png +0 -0
  74. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
  75. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
  76. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
  77. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
  78. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
  79. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
  80. package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
  81. package/flutter_app/pubspec.lock +2 -2
  82. package/flutter_app/pubspec.yaml +7 -1
  83. package/flutter_app/tool/branding_source/neoagent-icon-1024.png +0 -0
  84. package/flutter_app/tool/branding_source/neoagent-icon-128.png +0 -0
  85. package/flutter_app/tool/branding_source/neoagent-icon-16.png +0 -0
  86. package/flutter_app/tool/branding_source/neoagent-icon-180.png +0 -0
  87. package/flutter_app/tool/branding_source/neoagent-icon-192.png +0 -0
  88. package/flutter_app/tool/branding_source/neoagent-icon-256.png +0 -0
  89. package/flutter_app/tool/branding_source/neoagent-icon-32.png +0 -0
  90. package/flutter_app/tool/branding_source/neoagent-icon-48.png +0 -0
  91. package/flutter_app/tool/branding_source/neoagent-icon-512.png +0 -0
  92. package/flutter_app/tool/branding_source/neoagent-icon-64.png +0 -0
  93. package/flutter_app/tool/branding_source/neoagent-icon.svg +43 -0
  94. package/flutter_app/tool/generate_desktop_branding.py +154 -152
  95. package/flutter_app/web/favicon.png +0 -0
  96. package/flutter_app/web/favicon.svg +40 -9
  97. package/flutter_app/web/favicon_light.svg +43 -0
  98. package/flutter_app/web/icons/Icon-192-light.png +0 -0
  99. package/flutter_app/web/icons/Icon-192.png +0 -0
  100. package/flutter_app/web/icons/Icon-512-light.png +0 -0
  101. package/flutter_app/web/icons/Icon-512.png +0 -0
  102. package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
  103. package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
  104. package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
  105. package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
  106. package/flutter_app/windows/runner/main.cpp +7 -1
  107. package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
  108. package/lib/manager.js +445 -83
  109. package/package.json +17 -3
  110. package/runtime/paths.js +3 -1
  111. package/server/admin/access.js +198 -0
  112. package/server/admin/admin.css +268 -0
  113. package/server/admin/admin.js +348 -0
  114. package/server/admin/analytics.js +128 -0
  115. package/server/admin/index.html +1015 -0
  116. package/server/admin/login.html +290 -0
  117. package/server/admin/logo.svg +43 -0
  118. package/server/admin/sql.js +134 -0
  119. package/server/admin/users.js +147 -0
  120. package/server/config/origins.js +3 -1
  121. package/server/db/database.js +92 -0
  122. package/server/db/ftsQuery.js +27 -0
  123. package/server/http/routes.js +1 -0
  124. package/server/http/static.js +23 -6
  125. package/server/index.js +1 -1
  126. package/server/middleware/adminAuth.js +48 -0
  127. package/server/middleware/auth.js +1 -40
  128. package/server/public/.last_build_id +1 -1
  129. package/server/public/app_icon.png +0 -0
  130. package/server/public/assets/AssetManifest.bin +1 -1
  131. package/server/public/assets/AssetManifest.bin.json +1 -1
  132. package/server/public/assets/assets/branding/app_icon_1024.png +0 -0
  133. package/server/public/assets/assets/branding/app_icon_256.png +0 -0
  134. package/server/public/assets/assets/branding/app_icon_512.png +0 -0
  135. package/server/public/assets/assets/branding/app_icon_light_1024.png +0 -0
  136. package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
  137. package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
  138. package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
  139. package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
  140. package/server/public/assets/assets/branding/tray_icon_template.png +0 -0
  141. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  142. package/server/public/assets/web/icons/Icon-192.png +0 -0
  143. package/server/public/favicon.png +0 -0
  144. package/server/public/favicon.svg +40 -9
  145. package/server/public/favicon_light.svg +43 -0
  146. package/server/public/flutter_bootstrap.js +2 -2
  147. package/server/public/icons/Icon-192-light.png +0 -0
  148. package/server/public/icons/Icon-192.png +0 -0
  149. package/server/public/icons/Icon-512-light.png +0 -0
  150. package/server/public/icons/Icon-512.png +0 -0
  151. package/server/public/icons/Icon-maskable-192-light.png +0 -0
  152. package/server/public/icons/Icon-maskable-192.png +0 -0
  153. package/server/public/icons/Icon-maskable-512-light.png +0 -0
  154. package/server/public/icons/Icon-maskable-512.png +0 -0
  155. package/server/public/main.dart.js +88537 -85936
  156. package/server/routes/admin.js +632 -0
  157. package/server/routes/agent_profiles.js +3 -0
  158. package/server/routes/agents.js +1 -1
  159. package/server/routes/auth.js +3 -1
  160. package/server/routes/mcp.js +29 -13
  161. package/server/routes/memory.js +23 -1
  162. package/server/routes/messaging.js +2 -0
  163. package/server/routes/screenHistory.js +14 -8
  164. package/server/routes/settings.js +10 -2
  165. package/server/routes/stream.js +1 -1
  166. package/server/routes/triggers.js +4 -4
  167. package/server/routes/voice_assistant.js +36 -1
  168. package/server/routes/workspace.js +86 -0
  169. package/server/services/account/admin_two_factor.js +132 -0
  170. package/server/services/account/password_policy.js +6 -1
  171. package/server/services/ai/completion.js +44 -0
  172. package/server/services/ai/engine.js +321 -500
  173. package/server/services/ai/imageAnalysis.js +9 -5
  174. package/server/services/ai/logFormat.js +46 -0
  175. package/server/services/ai/loopPolicy.js +11 -0
  176. package/server/services/ai/messagingFallback.js +228 -0
  177. package/server/services/ai/models.js +194 -55
  178. package/server/services/ai/providerRetry.js +169 -0
  179. package/server/services/ai/providers/anthropic.js +15 -4
  180. package/server/services/ai/providers/google.js +38 -11
  181. package/server/services/ai/providers/grok.js +19 -68
  182. package/server/services/ai/providers/grokOauth.js +141 -0
  183. package/server/services/ai/providers/nvidia.js +154 -0
  184. package/server/services/ai/providers/ollama.js +76 -39
  185. package/server/services/ai/providers/openai.js +20 -31
  186. package/server/services/ai/providers/openaiCodex.js +6 -2
  187. package/server/services/ai/providers/openaiCompatible.js +70 -0
  188. package/server/services/ai/providers/openrouter.js +162 -0
  189. package/server/services/ai/settings.js +30 -0
  190. package/server/services/ai/systemPrompt.js +51 -29
  191. package/server/services/ai/taskAnalysis.js +60 -1
  192. package/server/services/ai/toolEvidence.js +207 -0
  193. package/server/services/ai/toolSelector.js +8 -1
  194. package/server/services/ai/tools.js +78 -13
  195. package/server/services/browser/extension/registry.js +94 -9
  196. package/server/services/desktop/registry.js +104 -1
  197. package/server/services/desktop/screenRecorder.js +208 -98
  198. package/server/services/desktop/screen_recorder_support.js +46 -0
  199. package/server/services/integrations/google/provider.js +13 -0
  200. package/server/services/integrations/home_assistant/constants.js +88 -0
  201. package/server/services/integrations/home_assistant/network.js +207 -0
  202. package/server/services/integrations/home_assistant/provider.js +249 -0
  203. package/server/services/integrations/home_assistant/snapshot.js +98 -0
  204. package/server/services/integrations/home_assistant/tools.js +101 -0
  205. package/server/services/integrations/manager.js +5 -0
  206. package/server/services/integrations/registry.js +2 -0
  207. package/server/services/integrations/trello/provider.js +8 -2
  208. package/server/services/manager.js +51 -53
  209. package/server/services/mcp/client.js +208 -268
  210. package/server/services/mcp/client_support.js +172 -0
  211. package/server/services/mcp/recovery.js +116 -0
  212. package/server/services/mcp/tool_operations.js +123 -0
  213. package/server/services/memory/ingestion.js +185 -370
  214. package/server/services/memory/ingestion_coverage.js +129 -0
  215. package/server/services/memory/ingestion_documents.js +123 -0
  216. package/server/services/memory/ingestion_support.js +171 -0
  217. package/server/services/memory/intelligence.js +181 -0
  218. package/server/services/memory/manager.js +476 -32
  219. package/server/services/messaging/automation.js +71 -134
  220. package/server/services/messaging/formatting_guides.js +26 -1
  221. package/server/services/messaging/inbound_queue.js +111 -0
  222. package/server/services/messaging/manager.js +25 -8
  223. package/server/services/messaging/typing_keepalive.js +110 -0
  224. package/server/services/streaming/android-stream.js +293 -40
  225. package/server/services/tasks/integration_runtime.js +4 -1
  226. package/server/services/tasks/runtime.js +415 -56
  227. package/server/services/tasks/task_repository.js +48 -6
  228. package/server/services/websocket.js +8 -2
  229. package/server/services/widgets/service.js +17 -1
  230. package/server/services/workspace/manager.js +116 -0
  231. package/server/utils/logger.js +44 -6
  232. package/server/utils/security.js +3 -0
  233. package/server/utils/version.js +29 -19
  234. package/server/services/memory/openhuman_uplift.test.js +0 -98
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+
3
+ const { resolveAgentId } = require('../agents/manager');
4
+ const {
5
+ buildCoverageForConnection,
6
+ sourceTypesForConnection,
7
+ } = require('./ingestion_support');
8
+
9
+ function listConnectionStatuses(service, userId, { agentId = null } = {}) {
10
+ const scopedAgentId = resolveAgentId(userId, agentId);
11
+ const connections = service.db.prepare(
12
+ `SELECT *
13
+ FROM integration_connections
14
+ WHERE user_id = ? AND agent_id = ?
15
+ ORDER BY updated_at DESC, id DESC`,
16
+ ).all(userId, scopedAgentId);
17
+ const jobs = service.memoryManager.listIngestionJobs(
18
+ userId,
19
+ { agentId: scopedAgentId, limit: 100 },
20
+ );
21
+ return connections.map((connection) => {
22
+ const latestJob = jobs.find(
23
+ (job) => Number(job.connectionId || 0) === Number(connection.id),
24
+ );
25
+ return {
26
+ connectionId: connection.id,
27
+ providerKey: connection.provider_key,
28
+ appKey: connection.app_key,
29
+ accountEmail: connection.account_email || null,
30
+ ...buildCoverageForConnection(connection, latestJob),
31
+ };
32
+ });
33
+ }
34
+
35
+ function decorateProviderSnapshot(service, snapshot, userId, agentId = null) {
36
+ if (!snapshot || typeof snapshot !== 'object') return snapshot;
37
+ const scopedAgentId = resolveAgentId(userId, agentId);
38
+ const connections = service.db.prepare(
39
+ `SELECT *
40
+ FROM integration_connections
41
+ WHERE user_id = ? AND agent_id = ? AND provider_key = ?`,
42
+ ).all(userId, scopedAgentId, snapshot.provider);
43
+ const jobs = service.memoryManager.listIngestionJobs(userId, {
44
+ agentId: scopedAgentId,
45
+ providerKey: snapshot.provider,
46
+ limit: 100,
47
+ });
48
+ const latestJobForConnection = (connectionId) =>
49
+ jobs.find((job) => Number(job.connectionId || 0) === Number(connectionId));
50
+
51
+ const connectionCoverage = connections.map((connection) => ({
52
+ connectionId: connection.id,
53
+ appKey: connection.app_key,
54
+ accountEmail: connection.account_email || null,
55
+ ...buildCoverageForConnection(connection, latestJobForConnection(connection.id)),
56
+ }));
57
+ const providerDomains = Array.from(
58
+ new Set(connectionCoverage.flatMap((item) => item.dataDomains || [])),
59
+ );
60
+ const providerJob = jobs[0] || null;
61
+ const decorated = {
62
+ ...snapshot,
63
+ memoryCoverage: {
64
+ supported: providerDomains.length > 0,
65
+ contributesToMemory: providerDomains.length > 0,
66
+ contributesToTaskExecution: Boolean(snapshot.connection?.connected),
67
+ status: providerJob?.status || (providerDomains.length > 0 ? 'ready' : 'not_supported'),
68
+ dataDomains: providerDomains,
69
+ documentCount: connectionCoverage.reduce(
70
+ (sum, item) => sum + Number(item.documentCount || 0),
71
+ 0,
72
+ ),
73
+ lastRefreshAt: providerJob?.completedAt || providerJob?.updatedAt || null,
74
+ nextRefreshAt: providerJob?.nextSyncAt || null,
75
+ error: providerJob?.error || null,
76
+ },
77
+ };
78
+
79
+ decorated.apps = (snapshot.apps || []).map((app) => {
80
+ const appConnections = connectionCoverage.filter((item) => item.appKey === app.id);
81
+ const dataDomains = Array.from(new Set([
82
+ ...sourceTypesForConnection(snapshot.provider, app.id),
83
+ ...appConnections.flatMap((item) => item.dataDomains || []),
84
+ ]));
85
+ const latest = appConnections[0] || null;
86
+ return {
87
+ ...app,
88
+ memoryCoverage: {
89
+ supported: dataDomains.length > 0,
90
+ contributesToMemory: dataDomains.length > 0 && app.connection?.connected === true,
91
+ contributesToTaskExecution: app.connection?.connected === true,
92
+ status: latest?.status || (dataDomains.length > 0 ? 'ready' : 'not_supported'),
93
+ dataDomains,
94
+ documentCount: appConnections.reduce(
95
+ (sum, item) => sum + Number(item.documentCount || 0),
96
+ 0,
97
+ ),
98
+ lastRefreshAt: latest?.lastRefreshAt || null,
99
+ nextRefreshAt: latest?.nextRefreshAt || null,
100
+ error: latest?.error || null,
101
+ },
102
+ accounts: (app.accounts || []).map((account) => {
103
+ const accountCoverage = appConnections.find(
104
+ (item) => Number(item.connectionId) === Number(account.connectionId),
105
+ );
106
+ return {
107
+ ...account,
108
+ memoryCoverage: accountCoverage || {
109
+ supported: dataDomains.length > 0,
110
+ contributesToMemory: false,
111
+ contributesToTaskExecution: app.connection?.connected === true,
112
+ status: dataDomains.length > 0 ? 'ready' : 'not_supported',
113
+ dataDomains,
114
+ documentCount: 0,
115
+ lastRefreshAt: null,
116
+ nextRefreshAt: null,
117
+ error: null,
118
+ },
119
+ };
120
+ }),
121
+ };
122
+ });
123
+ return decorated;
124
+ }
125
+
126
+ module.exports = {
127
+ decorateProviderSnapshot,
128
+ listConnectionStatuses,
129
+ };
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ const { v4: uuidv4 } = require('uuid');
4
+ const { resolveAgentId } = require('../agents/manager');
5
+ const {
6
+ SOURCE_MEMORY_CATEGORIES,
7
+ getFreshnessPolicy,
8
+ nextSyncFromPolicy,
9
+ normalizeDocument,
10
+ normalizeSourceType,
11
+ parseJsonObject,
12
+ safeTrim,
13
+ } = require('./ingestion_support');
14
+
15
+ async function ingestDocuments(service, userId, documents = [], options = {}) {
16
+ const agentId = resolveAgentId(userId, options.agentId || options.agent_id || null);
17
+ const normalizedDocs = (Array.isArray(documents) ? documents : [])
18
+ .map((document) => normalizeDocument(document, {
19
+ sourceType: options.sourceType,
20
+ providerKey: options.providerKey,
21
+ connectionId: options.connectionId,
22
+ sourceAccount: options.sourceAccount,
23
+ }));
24
+ const sourceType = normalizeSourceType(options.sourceType || normalizedDocs[0]?.sourceType);
25
+ const policy = getFreshnessPolicy(sourceType);
26
+ const jobId = uuidv4();
27
+ const connectionId = Number.isInteger(Number(options.connectionId))
28
+ ? Number(options.connectionId)
29
+ : 0;
30
+
31
+ service.memoryManager.recordIngestionJob(userId, {
32
+ id: jobId,
33
+ sourceType,
34
+ providerKey: safeTrim(options.providerKey, 80),
35
+ connectionId,
36
+ status: 'running',
37
+ freshnessPolicy: policy,
38
+ metadata: parseJsonObject(options.metadata, {}),
39
+ documentCount: 0,
40
+ nextSyncAt: nextSyncFromPolicy(policy),
41
+ }, { agentId });
42
+
43
+ const documentIds = [];
44
+ const memoryIds = [];
45
+ try {
46
+ for (const document of normalizedDocs) {
47
+ const documentId = service.memoryManager.upsertIngestionDocument(
48
+ userId,
49
+ document,
50
+ { agentId },
51
+ );
52
+ documentIds.push(documentId);
53
+ const memoryId = await service.memoryManager.saveMemory(
54
+ userId,
55
+ `${document.title}: ${document.summary || document.content}`,
56
+ SOURCE_MEMORY_CATEGORIES[document.normalizedType]
57
+ || SOURCE_MEMORY_CATEGORIES[document.sourceType]
58
+ || 'episodic',
59
+ document.salience,
60
+ {
61
+ agentId,
62
+ staleAfterDays: getFreshnessPolicy(document.sourceType).staleAfterDays,
63
+ sourceRef: {
64
+ sourceType: 'memory_ingestion',
65
+ sourceId: document.externalObjectId,
66
+ sourceLabel: document.title,
67
+ },
68
+ scope: {
69
+ scopeType: 'agent',
70
+ scopeId: agentId,
71
+ },
72
+ metadata: {
73
+ ingestionJobId: jobId,
74
+ ingestionDocumentId: documentId,
75
+ providerKey: document.providerKey || null,
76
+ connectionId: document.connectionId || null,
77
+ sourceType: document.sourceType,
78
+ },
79
+ },
80
+ );
81
+ if (memoryId) memoryIds.push(memoryId);
82
+ }
83
+
84
+ service.memoryManager.recordIngestionJob(userId, {
85
+ id: jobId,
86
+ sourceType,
87
+ providerKey: safeTrim(options.providerKey, 80),
88
+ connectionId,
89
+ status: 'completed',
90
+ freshnessPolicy: policy,
91
+ summary: { documentIds, memoryIds },
92
+ metadata: parseJsonObject(options.metadata, {}),
93
+ documentCount: documentIds.length,
94
+ completedAt: new Date().toISOString(),
95
+ nextSyncAt: nextSyncFromPolicy(policy),
96
+ }, { agentId });
97
+
98
+ const knowledgeViews = service.memoryManager.materializeKnowledgeViews(userId, { agentId });
99
+ return {
100
+ jobId,
101
+ status: 'completed',
102
+ documentIds,
103
+ memoryIds,
104
+ knowledgeViews,
105
+ };
106
+ } catch (err) {
107
+ service.memoryManager.recordIngestionJob(userId, {
108
+ id: jobId,
109
+ sourceType,
110
+ providerKey: safeTrim(options.providerKey, 80),
111
+ connectionId,
112
+ status: 'failed',
113
+ freshnessPolicy: policy,
114
+ documentCount: documentIds.length,
115
+ error: err.message,
116
+ completedAt: new Date().toISOString(),
117
+ nextSyncAt: nextSyncFromPolicy(policy),
118
+ }, { agentId });
119
+ throw err;
120
+ }
121
+ }
122
+
123
+ module.exports = { ingestDocuments };
@@ -0,0 +1,171 @@
1
+ 'use strict';
2
+
3
+ const { compactTextPayload } = require('../ai/preModelCompaction');
4
+
5
+ const SOURCE_TYPES = Object.freeze([
6
+ 'email',
7
+ 'calendar',
8
+ 'chat',
9
+ 'docs',
10
+ 'tickets',
11
+ 'repos',
12
+ 'files',
13
+ 'crm',
14
+ 'payments',
15
+ 'notes',
16
+ ]);
17
+
18
+ const FRESHNESS_POLICIES = Object.freeze({
19
+ email: Object.freeze({ intervalMinutes: 60, staleAfterDays: 14 }),
20
+ calendar: Object.freeze({ intervalMinutes: 180, staleAfterDays: 30 }),
21
+ chat: Object.freeze({ intervalMinutes: 60, staleAfterDays: 14 }),
22
+ docs: Object.freeze({ intervalMinutes: 360, staleAfterDays: 60 }),
23
+ tickets: Object.freeze({ intervalMinutes: 120, staleAfterDays: 30 }),
24
+ repos: Object.freeze({ intervalMinutes: 180, staleAfterDays: 45 }),
25
+ files: Object.freeze({ intervalMinutes: 360, staleAfterDays: 60 }),
26
+ crm: Object.freeze({ intervalMinutes: 240, staleAfterDays: 45 }),
27
+ payments: Object.freeze({ intervalMinutes: 360, staleAfterDays: 90 }),
28
+ notes: Object.freeze({ intervalMinutes: 360, staleAfterDays: 90 }),
29
+ });
30
+
31
+ const SOURCE_MEMORY_CATEGORIES = Object.freeze({
32
+ email: 'episodic',
33
+ calendar: 'events',
34
+ chat: 'episodic',
35
+ docs: 'projects',
36
+ tickets: 'tasks',
37
+ repos: 'projects',
38
+ files: 'episodic',
39
+ crm: 'contacts',
40
+ payments: 'events',
41
+ notes: 'episodic',
42
+ });
43
+
44
+ const INTEGRATION_SOURCE_TYPES = Object.freeze({
45
+ google_workspace: Object.freeze({
46
+ gmail: Object.freeze(['email']),
47
+ calendar: Object.freeze(['calendar']),
48
+ drive: Object.freeze(['files']),
49
+ docs: Object.freeze(['docs']),
50
+ sheets: Object.freeze(['docs', 'files']),
51
+ }),
52
+ microsoft_365: Object.freeze({
53
+ outlook: Object.freeze(['email']),
54
+ calendar: Object.freeze(['calendar']),
55
+ onedrive: Object.freeze(['files']),
56
+ teams: Object.freeze(['chat']),
57
+ }),
58
+ github: Object.freeze({
59
+ repos: Object.freeze(['repos', 'tickets']),
60
+ }),
61
+ slack: Object.freeze({
62
+ slack: Object.freeze(['chat']),
63
+ }),
64
+ whatsapp: Object.freeze({
65
+ personal: Object.freeze(['chat']),
66
+ }),
67
+ notion: Object.freeze({
68
+ notion: Object.freeze(['notes', 'docs']),
69
+ }),
70
+ trello: Object.freeze({
71
+ trello: Object.freeze(['tickets']),
72
+ }),
73
+ });
74
+
75
+ function safeTrim(value, maxLength = 240) {
76
+ return String(value || '').trim().slice(0, maxLength);
77
+ }
78
+
79
+ function parseJsonObject(value, fallback = {}) {
80
+ if (!value) return { ...fallback };
81
+ if (typeof value === 'object' && !Array.isArray(value)) return { ...value };
82
+ try {
83
+ const parsed = JSON.parse(String(value));
84
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
85
+ ? parsed
86
+ : { ...fallback };
87
+ } catch {
88
+ return { ...fallback };
89
+ }
90
+ }
91
+
92
+ function normalizeSourceType(value) {
93
+ const normalized = String(value || '').trim().toLowerCase();
94
+ return SOURCE_TYPES.includes(normalized) ? normalized : 'notes';
95
+ }
96
+
97
+ function getFreshnessPolicy(sourceType) {
98
+ const normalized = normalizeSourceType(sourceType);
99
+ return { ...FRESHNESS_POLICIES[normalized] };
100
+ }
101
+
102
+ function nextSyncFromPolicy(policy, now = Date.now()) {
103
+ const intervalMinutes = Math.max(15, Number(policy?.intervalMinutes) || 360);
104
+ return new Date(now + intervalMinutes * 60 * 1000).toISOString();
105
+ }
106
+
107
+ function normalizeDocument(raw = {}, defaults = {}) {
108
+ const sourceType = normalizeSourceType(raw.sourceType || defaults.sourceType);
109
+ const externalObjectId = safeTrim(raw.externalObjectId || raw.id || raw.sourceId, 180);
110
+ const contentSource = raw.content ?? raw.text ?? raw.body ?? '';
111
+ const content = safeTrim(contentSource, 12000);
112
+ if (!externalObjectId || !content) {
113
+ throw new Error('Normalized memory documents require externalObjectId and content.');
114
+ }
115
+ const compacted = compactTextPayload(content, { maxChars: 2400, maxLines: 50 });
116
+ return {
117
+ sourceType,
118
+ normalizedType: normalizeSourceType(raw.normalizedType || raw.memoryType || sourceType),
119
+ providerKey: safeTrim(raw.providerKey || defaults.providerKey, 80),
120
+ connectionId: Number.isInteger(Number(raw.connectionId ?? defaults.connectionId))
121
+ ? Number(raw.connectionId ?? defaults.connectionId)
122
+ : 0,
123
+ externalObjectId,
124
+ sourceAccount: safeTrim(raw.sourceAccount || defaults.sourceAccount, 180),
125
+ title: safeTrim(raw.title || raw.subject || raw.name || externalObjectId, 220),
126
+ content,
127
+ summary: safeTrim(raw.summary || compacted.text, 1200),
128
+ salience: Math.max(1, Math.min(10, Number(raw.salience) || 5)),
129
+ sourceTimestamp: safeTrim(raw.sourceTimestamp || raw.updatedAt || raw.createdAt, 80) || null,
130
+ metadata: {
131
+ ...parseJsonObject(raw.metadata, {}),
132
+ compaction: compacted.metrics,
133
+ },
134
+ payload: parseJsonObject(raw.payload, {}),
135
+ };
136
+ }
137
+
138
+ function sourceTypesForConnection(providerKey, appKey) {
139
+ const providerMap = INTEGRATION_SOURCE_TYPES[String(providerKey || '').trim()] || {};
140
+ return Array.from(providerMap[String(appKey || '').trim()] || []);
141
+ }
142
+
143
+ function buildCoverageForConnection(connection, latestJob = null) {
144
+ const dataDomains = sourceTypesForConnection(connection.provider_key, connection.app_key);
145
+ const supported = dataDomains.length > 0;
146
+ return {
147
+ supported,
148
+ contributesToMemory: supported,
149
+ contributesToTaskExecution: String(connection.status || '') === 'connected',
150
+ status: latestJob?.status || (supported ? 'ready' : 'not_supported'),
151
+ dataDomains,
152
+ lastRefreshAt: latestJob?.completedAt || latestJob?.updatedAt || null,
153
+ nextRefreshAt: latestJob?.nextSyncAt || null,
154
+ documentCount: Number(latestJob?.documentCount || 0),
155
+ error: latestJob?.error || null,
156
+ };
157
+ }
158
+
159
+ module.exports = {
160
+ FRESHNESS_POLICIES,
161
+ SOURCE_MEMORY_CATEGORIES,
162
+ SOURCE_TYPES,
163
+ buildCoverageForConnection,
164
+ getFreshnessPolicy,
165
+ nextSyncFromPolicy,
166
+ normalizeDocument,
167
+ normalizeSourceType,
168
+ parseJsonObject,
169
+ safeTrim,
170
+ sourceTypesForConnection,
171
+ };
@@ -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
+ };