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.
- package/.env.example +33 -3
- package/LICENSE +111 -56
- package/README.md +8 -3
- package/docs/configuration.md +8 -0
- package/docs/getting-started.md +9 -3
- package/docs/index.md +4 -0
- package/extensions/chrome-browser/background.mjs +36 -4
- package/extensions/chrome-browser/icons/icon128.png +0 -0
- package/extensions/chrome-browser/icons/icon16.png +0 -0
- package/extensions/chrome-browser/icons/icon48.png +0 -0
- package/extensions/chrome-browser/icons/logo.svg +39 -8
- package/extensions/chrome-browser/manifest.json +3 -3
- package/extensions/chrome-browser/popup.html +5 -1
- package/extensions/chrome-browser/popup.js +13 -0
- package/flutter_app/android/app/src/main/AndroidManifest.xml +2 -1
- package/flutter_app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/flutter_app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/flutter_app/assets/branding/app_icon_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_64.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
- package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
- package/flutter_app/assets/branding/onboarding_intro.mp4 +0 -0
- package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
- package/flutter_app/assets/branding/tray_icon_template.png +0 -0
- package/flutter_app/lib/features/location/location_service.dart +3 -0
- package/flutter_app/lib/features/onboarding/onboarding_chrome.dart +391 -382
- package/flutter_app/lib/features/onboarding/onboarding_companion_step.dart +743 -0
- package/flutter_app/lib/features/onboarding/onboarding_messaging_step.dart +18 -16
- package/flutter_app/lib/features/onboarding/onboarding_model_step.dart +19 -18
- package/flutter_app/lib/features/onboarding/onboarding_shell.dart +8 -1
- package/flutter_app/lib/features/onboarding/onboarding_video_step.dart +16 -13
- package/flutter_app/lib/features/onboarding/onboarding_welcome_step.dart +17 -13
- package/flutter_app/lib/main.dart +2 -0
- package/flutter_app/lib/main_account_settings.dart +10 -34
- package/flutter_app/lib/main_admin.dart +14 -1
- package/flutter_app/lib/main_app_shell.dart +377 -340
- package/flutter_app/lib/main_chat.dart +707 -227
- package/flutter_app/lib/main_controller.dart +197 -42
- package/flutter_app/lib/main_devices.dart +436 -4
- package/flutter_app/lib/main_integrations.dart +255 -6
- package/flutter_app/lib/main_launcher.dart +1 -1
- package/flutter_app/lib/main_model_picker.dart +685 -0
- package/flutter_app/lib/main_models.dart +212 -0
- package/flutter_app/lib/main_navigation.dart +1 -9
- package/flutter_app/lib/main_operations.dart +1417 -614
- package/flutter_app/lib/main_settings.dart +673 -871
- package/flutter_app/lib/main_shared.dart +971 -443
- package/flutter_app/lib/main_spacing.dart +4 -4
- package/flutter_app/lib/main_theme.dart +22 -14
- package/flutter_app/lib/main_unified.dart +5 -72
- package/flutter_app/lib/src/android_apk_drop_zone.dart +24 -0
- package/flutter_app/lib/src/android_apk_drop_zone_stub.dart +13 -0
- package/flutter_app/lib/src/android_apk_drop_zone_web.dart +219 -0
- package/flutter_app/lib/src/backend_client.dart +46 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +30 -7
- package/flutter_app/lib/src/desktop_companion_io.dart +71 -1
- package/flutter_app/lib/src/security/password_strength.dart +84 -0
- package/flutter_app/lib/src/stream_renderer.dart +42 -3
- package/flutter_app/lib/src/theme/palette.dart +76 -34
- package/flutter_app/linux/runner/resources/app_icon.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png +0 -0
- package/flutter_app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png +0 -0
- package/flutter_app/pubspec.lock +2 -2
- package/flutter_app/pubspec.yaml +7 -1
- package/flutter_app/tool/branding_source/neoagent-icon-1024.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-128.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-16.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-180.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-192.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-256.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-32.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-48.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-512.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon-64.png +0 -0
- package/flutter_app/tool/branding_source/neoagent-icon.svg +43 -0
- package/flutter_app/tool/generate_desktop_branding.py +154 -152
- package/flutter_app/web/favicon.png +0 -0
- package/flutter_app/web/favicon.svg +40 -9
- package/flutter_app/web/favicon_light.svg +43 -0
- package/flutter_app/web/icons/Icon-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-192.png +0 -0
- package/flutter_app/web/icons/Icon-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-512.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-192.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
- package/flutter_app/web/icons/Icon-maskable-512.png +0 -0
- package/flutter_app/windows/runner/main.cpp +7 -1
- package/flutter_app/windows/runner/resources/app_icon.ico +0 -0
- package/lib/manager.js +445 -83
- package/package.json +17 -3
- package/runtime/paths.js +3 -1
- package/server/admin/access.js +198 -0
- package/server/admin/admin.css +268 -0
- package/server/admin/admin.js +348 -0
- package/server/admin/analytics.js +128 -0
- package/server/admin/index.html +1015 -0
- package/server/admin/login.html +290 -0
- package/server/admin/logo.svg +43 -0
- package/server/admin/sql.js +134 -0
- package/server/admin/users.js +147 -0
- package/server/config/origins.js +3 -1
- package/server/db/database.js +92 -0
- package/server/db/ftsQuery.js +27 -0
- package/server/http/routes.js +1 -0
- package/server/http/static.js +23 -6
- package/server/index.js +1 -1
- package/server/middleware/adminAuth.js +48 -0
- package/server/middleware/auth.js +1 -40
- package/server/public/.last_build_id +1 -1
- package/server/public/app_icon.png +0 -0
- package/server/public/assets/AssetManifest.bin +1 -1
- package/server/public/assets/AssetManifest.bin.json +1 -1
- package/server/public/assets/assets/branding/app_icon_1024.png +0 -0
- package/server/public/assets/assets/branding/app_icon_256.png +0 -0
- package/server/public/assets/assets/branding/app_icon_512.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_1024.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
- package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
- package/server/public/assets/assets/branding/onboarding_intro.mp4 +0 -0
- package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
- package/server/public/assets/assets/branding/tray_icon_template.png +0 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/web/icons/Icon-192.png +0 -0
- package/server/public/favicon.png +0 -0
- package/server/public/favicon.svg +40 -9
- package/server/public/favicon_light.svg +43 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/icons/Icon-192-light.png +0 -0
- package/server/public/icons/Icon-192.png +0 -0
- package/server/public/icons/Icon-512-light.png +0 -0
- package/server/public/icons/Icon-512.png +0 -0
- package/server/public/icons/Icon-maskable-192-light.png +0 -0
- package/server/public/icons/Icon-maskable-192.png +0 -0
- package/server/public/icons/Icon-maskable-512-light.png +0 -0
- package/server/public/icons/Icon-maskable-512.png +0 -0
- package/server/public/main.dart.js +88537 -85936
- package/server/routes/admin.js +632 -0
- package/server/routes/agent_profiles.js +3 -0
- package/server/routes/agents.js +1 -1
- package/server/routes/auth.js +3 -1
- package/server/routes/mcp.js +29 -13
- package/server/routes/memory.js +23 -1
- package/server/routes/messaging.js +2 -0
- package/server/routes/screenHistory.js +14 -8
- package/server/routes/settings.js +10 -2
- package/server/routes/stream.js +1 -1
- package/server/routes/triggers.js +4 -4
- package/server/routes/voice_assistant.js +36 -1
- package/server/routes/workspace.js +86 -0
- package/server/services/account/admin_two_factor.js +132 -0
- package/server/services/account/password_policy.js +6 -1
- package/server/services/ai/completion.js +44 -0
- package/server/services/ai/engine.js +321 -500
- package/server/services/ai/imageAnalysis.js +9 -5
- package/server/services/ai/logFormat.js +46 -0
- package/server/services/ai/loopPolicy.js +11 -0
- package/server/services/ai/messagingFallback.js +228 -0
- package/server/services/ai/models.js +194 -55
- package/server/services/ai/providerRetry.js +169 -0
- package/server/services/ai/providers/anthropic.js +15 -4
- package/server/services/ai/providers/google.js +38 -11
- package/server/services/ai/providers/grok.js +19 -68
- package/server/services/ai/providers/grokOauth.js +141 -0
- package/server/services/ai/providers/nvidia.js +154 -0
- package/server/services/ai/providers/ollama.js +76 -39
- package/server/services/ai/providers/openai.js +20 -31
- package/server/services/ai/providers/openaiCodex.js +6 -2
- package/server/services/ai/providers/openaiCompatible.js +70 -0
- package/server/services/ai/providers/openrouter.js +162 -0
- package/server/services/ai/settings.js +30 -0
- package/server/services/ai/systemPrompt.js +51 -29
- package/server/services/ai/taskAnalysis.js +60 -1
- package/server/services/ai/toolEvidence.js +207 -0
- package/server/services/ai/toolSelector.js +8 -1
- package/server/services/ai/tools.js +78 -13
- package/server/services/browser/extension/registry.js +94 -9
- package/server/services/desktop/registry.js +104 -1
- package/server/services/desktop/screenRecorder.js +208 -98
- package/server/services/desktop/screen_recorder_support.js +46 -0
- package/server/services/integrations/google/provider.js +13 -0
- package/server/services/integrations/home_assistant/constants.js +88 -0
- package/server/services/integrations/home_assistant/network.js +207 -0
- package/server/services/integrations/home_assistant/provider.js +249 -0
- package/server/services/integrations/home_assistant/snapshot.js +98 -0
- package/server/services/integrations/home_assistant/tools.js +101 -0
- package/server/services/integrations/manager.js +5 -0
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/trello/provider.js +8 -2
- package/server/services/manager.js +51 -53
- package/server/services/mcp/client.js +208 -268
- package/server/services/mcp/client_support.js +172 -0
- package/server/services/mcp/recovery.js +116 -0
- package/server/services/mcp/tool_operations.js +123 -0
- package/server/services/memory/ingestion.js +185 -370
- package/server/services/memory/ingestion_coverage.js +129 -0
- package/server/services/memory/ingestion_documents.js +123 -0
- package/server/services/memory/ingestion_support.js +171 -0
- package/server/services/memory/intelligence.js +181 -0
- package/server/services/memory/manager.js +476 -32
- package/server/services/messaging/automation.js +71 -134
- package/server/services/messaging/formatting_guides.js +26 -1
- package/server/services/messaging/inbound_queue.js +111 -0
- package/server/services/messaging/manager.js +25 -8
- package/server/services/messaging/typing_keepalive.js +110 -0
- package/server/services/streaming/android-stream.js +293 -40
- package/server/services/tasks/integration_runtime.js +4 -1
- package/server/services/tasks/runtime.js +415 -56
- package/server/services/tasks/task_repository.js +48 -6
- package/server/services/websocket.js +8 -2
- package/server/services/widgets/service.js +17 -1
- package/server/services/workspace/manager.js +116 -0
- package/server/utils/logger.js +44 -6
- package/server/utils/security.js +3 -0
- package/server/utils/version.js +29 -19
- package/server/services/memory/openhuman_uplift.test.js +0 -98
|
@@ -18,9 +18,12 @@ const {
|
|
|
18
18
|
buildVoiceMessagingRunOptions,
|
|
19
19
|
isVoiceLikeMessage,
|
|
20
20
|
} = require('../voice/runtime');
|
|
21
|
+
const { getErrorMessage } = require('../bootstrap_helpers');
|
|
22
|
+
const { processInboundQueue } = require('./inbound_queue');
|
|
23
|
+
const { startTypingKeepalive } = require('./typing_keepalive');
|
|
21
24
|
|
|
22
25
|
function registerMessagingAutomation({ app, io, messagingManager, agentEngine }) {
|
|
23
|
-
const userQueues =
|
|
26
|
+
const userQueues = Object.create(null);
|
|
24
27
|
app.locals.userQueues = userQueues;
|
|
25
28
|
|
|
26
29
|
messagingManager.registerHandler(async (userId, msg) => {
|
|
@@ -100,62 +103,85 @@ function registerMessagingAutomation({ app, io, messagingManager, agentEngine })
|
|
|
100
103
|
messagingManager,
|
|
101
104
|
agentEngine,
|
|
102
105
|
userId,
|
|
103
|
-
msg
|
|
106
|
+
msg,
|
|
107
|
+
onProcessingError: ({ error, runId, failedMessage }) => {
|
|
108
|
+
const errorMessage = getErrorMessage(error);
|
|
109
|
+
console.error(
|
|
110
|
+
`[MessagingAutomation] Agent run failed platform=${failedMessage.platform} user=${userId}:`,
|
|
111
|
+
errorMessage
|
|
112
|
+
);
|
|
113
|
+
io.to(`user:${userId}`).emit('messaging:error', {
|
|
114
|
+
error: `NeoAgent could not finish the ${failedMessage.platform} request. Check the run history for details.`,
|
|
115
|
+
platform: failedMessage.platform,
|
|
116
|
+
chatId: failedMessage.chatId,
|
|
117
|
+
runId
|
|
118
|
+
});
|
|
119
|
+
}
|
|
104
120
|
});
|
|
105
121
|
});
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
async function processQueuedMessage({
|
|
109
125
|
userQueues,
|
|
126
|
+
messagingManager,
|
|
127
|
+
agentEngine,
|
|
128
|
+
userId,
|
|
129
|
+
msg,
|
|
130
|
+
onProcessingError = null
|
|
131
|
+
}) {
|
|
132
|
+
return processInboundQueue({
|
|
133
|
+
userQueues,
|
|
134
|
+
userId,
|
|
135
|
+
msg,
|
|
136
|
+
executeMessage: (queuedMessage) =>
|
|
137
|
+
executeQueuedMessage({
|
|
138
|
+
messagingManager,
|
|
139
|
+
agentEngine,
|
|
140
|
+
userId,
|
|
141
|
+
msg: queuedMessage
|
|
142
|
+
}),
|
|
143
|
+
onProcessingError
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function executeQueuedMessage({
|
|
110
148
|
messagingManager,
|
|
111
149
|
agentEngine,
|
|
112
150
|
userId,
|
|
113
151
|
msg
|
|
114
152
|
}) {
|
|
115
153
|
const agentId = msg.agentId || null;
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
154
|
+
const runId = randomUUID();
|
|
155
|
+
const reportSideEffectError = (operation, error) => {
|
|
156
|
+
console.warn(
|
|
157
|
+
`[MessagingAutomation] ${operation} failed platform=${msg.platform} user=${userId}:`,
|
|
158
|
+
getErrorMessage(error)
|
|
159
|
+
);
|
|
160
|
+
};
|
|
121
161
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
162
|
+
try {
|
|
163
|
+
await messagingManager.markRead(
|
|
164
|
+
userId,
|
|
165
|
+
msg.platform,
|
|
166
|
+
msg.chatId,
|
|
167
|
+
msg.messageId,
|
|
168
|
+
{ agentId }
|
|
169
|
+
);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
reportSideEffectError('mark read', error);
|
|
125
172
|
}
|
|
126
173
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
last.messageId = msg.messageId;
|
|
137
|
-
} else {
|
|
138
|
-
queue.pending.push({ ...msg });
|
|
139
|
-
}
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
174
|
+
const stopTypingKeepalive = startTypingKeepalive({
|
|
175
|
+
messagingManager,
|
|
176
|
+
userId,
|
|
177
|
+
agentId,
|
|
178
|
+
runId,
|
|
179
|
+
platform: msg.platform,
|
|
180
|
+
chatId: msg.chatId,
|
|
181
|
+
onError: reportSideEffectError
|
|
182
|
+
});
|
|
142
183
|
|
|
143
|
-
queue.running = true;
|
|
144
|
-
let stopTypingKeepalive = async () => {};
|
|
145
184
|
try {
|
|
146
|
-
const runId = randomUUID();
|
|
147
|
-
await messagingManager
|
|
148
|
-
.markRead(userId, msg.platform, msg.chatId, msg.messageId, { agentId })
|
|
149
|
-
.catch(() => {});
|
|
150
|
-
stopTypingKeepalive = startTypingKeepalive({
|
|
151
|
-
messagingManager,
|
|
152
|
-
userId,
|
|
153
|
-
agentId,
|
|
154
|
-
runId,
|
|
155
|
-
platform: msg.platform,
|
|
156
|
-
chatId: msg.chatId
|
|
157
|
-
});
|
|
158
|
-
|
|
159
185
|
const prompt = buildIncomingPrompt(msg);
|
|
160
186
|
const conversationId = ensureConversation(userId, msg);
|
|
161
187
|
const runOptions = isVoiceLikeMessage(msg)
|
|
@@ -182,105 +208,15 @@ async function processQueuedMessage({
|
|
|
182
208
|
];
|
|
183
209
|
}
|
|
184
210
|
|
|
185
|
-
await agentEngine.run(userId, prompt, runOptions);
|
|
211
|
+
const result = await agentEngine.run(userId, prompt, runOptions);
|
|
212
|
+
return { runId, result, error: null };
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return { runId, result: null, error };
|
|
186
215
|
} finally {
|
|
187
216
|
await stopTypingKeepalive();
|
|
188
|
-
if (queue.cancelRequested) {
|
|
189
|
-
queue.pending = [];
|
|
190
|
-
queue.running = false;
|
|
191
|
-
queue.cancelRequested = false;
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
queue.running = false;
|
|
195
|
-
if (queue.pending.length > 0) {
|
|
196
|
-
const next = queue.pending.shift();
|
|
197
|
-
await processQueuedMessage({
|
|
198
|
-
userQueues,
|
|
199
|
-
messagingManager,
|
|
200
|
-
agentEngine,
|
|
201
|
-
userId,
|
|
202
|
-
msg: next
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
|
|
208
|
-
function startTypingKeepalive({
|
|
209
|
-
messagingManager,
|
|
210
|
-
userId,
|
|
211
|
-
agentId,
|
|
212
|
-
runId,
|
|
213
|
-
platform,
|
|
214
|
-
chatId,
|
|
215
|
-
intervalMs = 4000
|
|
216
|
-
}) {
|
|
217
|
-
let stopped = false;
|
|
218
|
-
let timer = null;
|
|
219
|
-
let releaseWait = null;
|
|
220
|
-
let stopPromise = null;
|
|
221
|
-
|
|
222
|
-
const matchesRunDelivery = (event) => (
|
|
223
|
-
event?.runId
|
|
224
|
-
&& runId
|
|
225
|
-
&& event.runId === runId
|
|
226
|
-
&& event.userId === userId
|
|
227
|
-
&& event.platform === platform
|
|
228
|
-
&& event.to === chatId
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
const onMessageSent = (event) => {
|
|
232
|
-
if (matchesRunDelivery(event) && event.deliveryKind !== 'interim') {
|
|
233
|
-
stop().catch(() => {});
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
if (typeof messagingManager?.on === 'function' && typeof messagingManager?.off === 'function') {
|
|
238
|
-
messagingManager.on('message_sent', onMessageSent);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const wait = () =>
|
|
242
|
-
new Promise((resolve) => {
|
|
243
|
-
releaseWait = resolve;
|
|
244
|
-
timer = setTimeout(resolve, intervalMs);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const loop = (async () => {
|
|
248
|
-
while (!stopped) {
|
|
249
|
-
await messagingManager
|
|
250
|
-
.sendTyping(userId, platform, chatId, true, { agentId })
|
|
251
|
-
.catch(() => {});
|
|
252
|
-
|
|
253
|
-
if (stopped) break;
|
|
254
|
-
await wait();
|
|
255
|
-
}
|
|
256
|
-
})();
|
|
257
|
-
|
|
258
|
-
const stop = async () => {
|
|
259
|
-
if (stopPromise) return stopPromise;
|
|
260
|
-
stopPromise = (async () => {
|
|
261
|
-
if (typeof messagingManager?.off === 'function') {
|
|
262
|
-
messagingManager.off('message_sent', onMessageSent);
|
|
263
|
-
}
|
|
264
|
-
stopped = true;
|
|
265
|
-
if (timer) {
|
|
266
|
-
clearTimeout(timer);
|
|
267
|
-
timer = null;
|
|
268
|
-
}
|
|
269
|
-
if (releaseWait) {
|
|
270
|
-
releaseWait();
|
|
271
|
-
releaseWait = null;
|
|
272
|
-
}
|
|
273
|
-
await loop.catch(() => {});
|
|
274
|
-
await messagingManager
|
|
275
|
-
.sendTyping(userId, platform, chatId, false, { agentId })
|
|
276
|
-
.catch(() => {});
|
|
277
|
-
})();
|
|
278
|
-
return stopPromise;
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
return stop;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
220
|
function ensureConversation(userId, msg) {
|
|
285
221
|
const agentId = msg.agentId || null;
|
|
286
222
|
let conversation = db
|
|
@@ -419,6 +355,7 @@ function emitBlockedSenderSuggestion({ io, userId, msg }) {
|
|
|
419
355
|
module.exports = {
|
|
420
356
|
buildIncomingPrompt,
|
|
421
357
|
buildSenderIdentityBlock,
|
|
358
|
+
executeQueuedMessage,
|
|
422
359
|
isAllowedMessagingSender,
|
|
423
360
|
processQueuedMessage,
|
|
424
361
|
registerMessagingAutomation,
|
|
@@ -99,9 +99,34 @@ function adaptSpokenFormatting(text) {
|
|
|
99
99
|
.trim();
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function coerceMessageContent(content) {
|
|
103
|
+
if (typeof content === 'string') return content;
|
|
104
|
+
if (content == null) return '';
|
|
105
|
+
if (typeof content === 'number' || typeof content === 'boolean' || typeof content === 'bigint') {
|
|
106
|
+
return String(content);
|
|
107
|
+
}
|
|
108
|
+
if (typeof content !== 'object') return String(content || '');
|
|
109
|
+
|
|
110
|
+
for (const key of ['text', 'content', 'message', 'summary']) {
|
|
111
|
+
if (typeof content[key] === 'string' && content[key].trim()) {
|
|
112
|
+
return content[key];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (content.result != null && content.result !== content) {
|
|
117
|
+
return coerceMessageContent(content.result);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
return JSON.stringify(content, null, 2);
|
|
122
|
+
} catch {
|
|
123
|
+
return String(content || '');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
102
127
|
function normalizeOutgoingMessageForPlatform(platform, content, options = {}) {
|
|
103
128
|
const profile = getPlatformFormattingProfile(platform);
|
|
104
|
-
let text =
|
|
129
|
+
let text = coerceMessageContent(content);
|
|
105
130
|
|
|
106
131
|
if (options.stripNoResponseMarker !== false) {
|
|
107
132
|
text = text.replace(/\[NO RESPONSE\]/gi, '');
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getErrorMessage } = require('../bootstrap_helpers');
|
|
4
|
+
|
|
5
|
+
async function processInboundQueue({
|
|
6
|
+
userQueues,
|
|
7
|
+
userId,
|
|
8
|
+
msg,
|
|
9
|
+
executeMessage,
|
|
10
|
+
onProcessingError = null
|
|
11
|
+
}) {
|
|
12
|
+
const agentId = msg.agentId || null;
|
|
13
|
+
const queueKey = `${userId}:${agentId || 'main'}`;
|
|
14
|
+
if (!userQueues[queueKey]) {
|
|
15
|
+
userQueues[queueKey] = { running: false, pending: [], cancelRequested: false };
|
|
16
|
+
}
|
|
17
|
+
const queue = userQueues[queueKey];
|
|
18
|
+
|
|
19
|
+
if (queue.cancelRequested && !queue.running) {
|
|
20
|
+
queue.pending = [];
|
|
21
|
+
queue.cancelRequested = false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (queue.running) {
|
|
25
|
+
const last = queue.pending[queue.pending.length - 1];
|
|
26
|
+
if (
|
|
27
|
+
last
|
|
28
|
+
&& last.platform === msg.platform
|
|
29
|
+
&& last.chatId === msg.chatId
|
|
30
|
+
&& String(last.sender || '') === String(msg.sender || '')
|
|
31
|
+
) {
|
|
32
|
+
last.content += `\n${msg.content}`;
|
|
33
|
+
last.messageId = msg.messageId;
|
|
34
|
+
} else {
|
|
35
|
+
queue.pending.push({ ...msg });
|
|
36
|
+
}
|
|
37
|
+
return { queued: true };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
queue.running = true;
|
|
41
|
+
let currentMessage = msg;
|
|
42
|
+
let processedCount = 0;
|
|
43
|
+
let failedCount = 0;
|
|
44
|
+
let cancelled = false;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
while (currentMessage) {
|
|
48
|
+
let outcome;
|
|
49
|
+
try {
|
|
50
|
+
outcome = await executeMessage(currentMessage);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
outcome = { runId: null, result: null, error };
|
|
53
|
+
}
|
|
54
|
+
processedCount += 1;
|
|
55
|
+
|
|
56
|
+
if (outcome?.error) {
|
|
57
|
+
failedCount += 1;
|
|
58
|
+
await notifyProcessingError(onProcessingError, {
|
|
59
|
+
error: outcome.error,
|
|
60
|
+
runId: outcome.runId,
|
|
61
|
+
userId,
|
|
62
|
+
failedMessage: currentMessage
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (queue.cancelRequested) {
|
|
67
|
+
queue.pending = [];
|
|
68
|
+
cancelled = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
currentMessage = queue.pending.shift() || null;
|
|
73
|
+
}
|
|
74
|
+
} finally {
|
|
75
|
+
queue.running = false;
|
|
76
|
+
queue.pending = [];
|
|
77
|
+
queue.cancelRequested = false;
|
|
78
|
+
if (userQueues[queueKey] === queue) {
|
|
79
|
+
delete userQueues[queueKey];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
processedCount,
|
|
85
|
+
failedCount,
|
|
86
|
+
cancelled
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function notifyProcessingError(handler, details) {
|
|
91
|
+
if (typeof handler !== 'function') {
|
|
92
|
+
console.error(
|
|
93
|
+
`[MessagingAutomation] Agent run failed platform=${details.failedMessage.platform} user=${details.userId}:`,
|
|
94
|
+
getErrorMessage(details.error)
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
await handler(details);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(
|
|
103
|
+
'[MessagingAutomation] Failed to report an agent run error:',
|
|
104
|
+
getErrorMessage(error)
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
processInboundQueue
|
|
111
|
+
};
|
|
@@ -104,6 +104,9 @@ class MessagingManager extends EventEmitter {
|
|
|
104
104
|
return null;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
const normalizedIncomingContent = normalizeOutgoingMessageForPlatform(platformName, msg?.content, {
|
|
108
|
+
stripNoResponseMarker: false
|
|
109
|
+
});
|
|
107
110
|
const agentId = this._agentId(userId, {
|
|
108
111
|
...options,
|
|
109
112
|
agentId: options?.agentId ?? msg?.agentId ?? null,
|
|
@@ -135,7 +138,7 @@ class MessagingManager extends EventEmitter {
|
|
|
135
138
|
userId,
|
|
136
139
|
agentId,
|
|
137
140
|
'user',
|
|
138
|
-
|
|
141
|
+
normalizedIncomingContent,
|
|
139
142
|
platformName,
|
|
140
143
|
msg.messageId,
|
|
141
144
|
msg.chatId,
|
|
@@ -143,7 +146,7 @@ class MessagingManager extends EventEmitter {
|
|
|
143
146
|
msg.timestamp,
|
|
144
147
|
);
|
|
145
148
|
|
|
146
|
-
const enrichedMsg = { ...msg, agentId, platform: platformName };
|
|
149
|
+
const enrichedMsg = { ...msg, content: normalizedIncomingContent, agentId, platform: platformName };
|
|
147
150
|
|
|
148
151
|
if (this.isShuttingDown) {
|
|
149
152
|
return enrichedMsg;
|
|
@@ -317,6 +320,9 @@ class MessagingManager extends EventEmitter {
|
|
|
317
320
|
config.userId = userId;
|
|
318
321
|
config.agentId = agentId;
|
|
319
322
|
config.accessPolicy = this._loadAccessPolicy(userId, agentId, platformName);
|
|
323
|
+
const existingConnection = db
|
|
324
|
+
.prepare('SELECT id, status FROM platform_connections WHERE user_id = ? AND agent_id = ? AND platform = ?')
|
|
325
|
+
.get(userId, agentId, platformName);
|
|
320
326
|
const PlatformClass = this.platformTypes[platformName];
|
|
321
327
|
if (!PlatformClass) throw new Error(`Unknown platform: ${platformName}`);
|
|
322
328
|
if (platformName === 'meshtastic' && !readMeshtasticEnabled()) {
|
|
@@ -325,7 +331,18 @@ class MessagingManager extends EventEmitter {
|
|
|
325
331
|
|
|
326
332
|
if (platformName === 'whatsapp' && !config.authDir) {
|
|
327
333
|
config.authDir = this._scopedPlatformAuthDir(userId, agentId, platformName);
|
|
328
|
-
|
|
334
|
+
let shouldMigrateLegacyAuth = true;
|
|
335
|
+
if (
|
|
336
|
+
existingConnection &&
|
|
337
|
+
existingConnection.status !== 'connected' &&
|
|
338
|
+
existingConnection.status !== 'awaiting_qr'
|
|
339
|
+
) {
|
|
340
|
+
fs.rmSync(config.authDir, { recursive: true, force: true });
|
|
341
|
+
shouldMigrateLegacyAuth = false;
|
|
342
|
+
}
|
|
343
|
+
if (shouldMigrateLegacyAuth) {
|
|
344
|
+
this._maybeMigrateLegacyWhatsAppAuth(config.authDir);
|
|
345
|
+
}
|
|
329
346
|
}
|
|
330
347
|
|
|
331
348
|
// For Telnyx, inject saved whitelist and voice secret into config before constructing
|
|
@@ -443,8 +460,7 @@ class MessagingManager extends EventEmitter {
|
|
|
443
460
|
await this.ingestMessage(userId, platformName, msg, { agentId });
|
|
444
461
|
});
|
|
445
462
|
|
|
446
|
-
|
|
447
|
-
if (!existing) {
|
|
463
|
+
if (!existingConnection) {
|
|
448
464
|
db.prepare('INSERT INTO platform_connections (user_id, agent_id, platform, config, status) VALUES (?, ?, ?, ?, ?)')
|
|
449
465
|
.run(userId, agentId, platformName, storedConfig, 'connecting');
|
|
450
466
|
} else {
|
|
@@ -460,10 +476,11 @@ class MessagingManager extends EventEmitter {
|
|
|
460
476
|
const agentId = this._agentId(userId, options);
|
|
461
477
|
const key = this._key(userId, agentId, platformName);
|
|
462
478
|
const platform = this.platforms.get(key);
|
|
463
|
-
if (!platform) return { status: 'not_connected' };
|
|
464
479
|
|
|
465
|
-
|
|
466
|
-
|
|
480
|
+
if (platform) {
|
|
481
|
+
await platform.disconnect();
|
|
482
|
+
this.platforms.delete(key);
|
|
483
|
+
}
|
|
467
484
|
|
|
468
485
|
db.prepare('UPDATE platform_connections SET status = ? WHERE user_id = ? AND agent_id = ? AND platform = ?')
|
|
469
486
|
.run('disconnected', userId, agentId, platformName);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getErrorMessage } = require('../bootstrap_helpers');
|
|
4
|
+
|
|
5
|
+
function startTypingKeepalive({
|
|
6
|
+
messagingManager,
|
|
7
|
+
userId,
|
|
8
|
+
agentId,
|
|
9
|
+
runId,
|
|
10
|
+
platform,
|
|
11
|
+
chatId,
|
|
12
|
+
intervalMs = 4000,
|
|
13
|
+
onError = null
|
|
14
|
+
}) {
|
|
15
|
+
let stopped = false;
|
|
16
|
+
let timer = null;
|
|
17
|
+
let releaseWait = null;
|
|
18
|
+
let stopPromise = null;
|
|
19
|
+
const reportedFailures = new Set();
|
|
20
|
+
|
|
21
|
+
const reportFailure = (operation, error) => {
|
|
22
|
+
if (reportedFailures.has(operation)) return;
|
|
23
|
+
reportedFailures.add(operation);
|
|
24
|
+
if (typeof onError === 'function') {
|
|
25
|
+
try {
|
|
26
|
+
onError(operation, error);
|
|
27
|
+
} catch (reportError) {
|
|
28
|
+
console.error(
|
|
29
|
+
'[MessagingAutomation] Typing failure reporter failed:',
|
|
30
|
+
getErrorMessage(reportError)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const matchesRunDelivery = (event) => (
|
|
37
|
+
event?.runId
|
|
38
|
+
&& runId
|
|
39
|
+
&& event.runId === runId
|
|
40
|
+
&& event.userId === userId
|
|
41
|
+
&& event.platform === platform
|
|
42
|
+
&& event.to === chatId
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const onMessageSent = (event) => {
|
|
46
|
+
if (matchesRunDelivery(event) && event.deliveryKind !== 'interim') {
|
|
47
|
+
stop().catch((error) => reportFailure('stop typing keepalive', error));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (typeof messagingManager?.on === 'function' && typeof messagingManager?.off === 'function') {
|
|
52
|
+
messagingManager.on('message_sent', onMessageSent);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const wait = () =>
|
|
56
|
+
new Promise((resolve) => {
|
|
57
|
+
releaseWait = resolve;
|
|
58
|
+
timer = setTimeout(resolve, intervalMs);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const sendTyping = async (isTyping, operation) => {
|
|
62
|
+
try {
|
|
63
|
+
await messagingManager.sendTyping(
|
|
64
|
+
userId,
|
|
65
|
+
platform,
|
|
66
|
+
chatId,
|
|
67
|
+
isTyping,
|
|
68
|
+
{ agentId }
|
|
69
|
+
);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
reportFailure(operation, error);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const loop = (async () => {
|
|
76
|
+
while (!stopped) {
|
|
77
|
+
await sendTyping(true, 'send typing indicator');
|
|
78
|
+
|
|
79
|
+
if (stopped) break;
|
|
80
|
+
await wait();
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
const stop = async () => {
|
|
85
|
+
if (stopPromise) return stopPromise;
|
|
86
|
+
stopPromise = (async () => {
|
|
87
|
+
if (typeof messagingManager?.off === 'function') {
|
|
88
|
+
messagingManager.off('message_sent', onMessageSent);
|
|
89
|
+
}
|
|
90
|
+
stopped = true;
|
|
91
|
+
if (timer) {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
timer = null;
|
|
94
|
+
}
|
|
95
|
+
if (releaseWait) {
|
|
96
|
+
releaseWait();
|
|
97
|
+
releaseWait = null;
|
|
98
|
+
}
|
|
99
|
+
await loop.catch((error) => reportFailure('typing keepalive loop', error));
|
|
100
|
+
await sendTyping(false, 'clear typing indicator');
|
|
101
|
+
})();
|
|
102
|
+
return stopPromise;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return stop;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
startTypingKeepalive
|
|
110
|
+
};
|