neoagent 2.2.1-beta.6 → 2.2.1-beta.8
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/docs/automation.md +2 -2
- package/docs/capabilities.md +7 -10
- package/docs/hardware.md +4 -7
- package/docs/index.md +7 -7
- package/docs/integrations.md +1 -1
- package/docs/migration.md +238 -0
- package/docs/operations.md +1 -1
- package/docs/why-neoagent.md +2 -2
- package/lib/manager.js +99 -2
- package/lib/migrations.js +409 -0
- package/package.json +1 -1
- package/server/catalog_sources/store-bundles/skills/productivity/migration/SKILL.md +173 -0
- package/server/db/database.js +76 -61
- package/server/http/routes.js +1 -2
- package/server/public/assets/AssetManifest.json +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +75830 -74189
- package/server/routes/auth.js +13 -5
- package/server/routes/integrations.js +22 -0
- package/server/routes/messaging.js +41 -5
- package/server/routes/settings.js +1 -0
- package/server/routes/{scheduler.js → tasks.js} +31 -29
- package/server/routes/widgets.js +7 -7
- package/server/services/ai/capabilityHealth.js +4 -4
- package/server/services/ai/engine.js +9 -9
- package/server/services/ai/systemPrompt.js +7 -7
- package/server/services/ai/taskAnalysis.js +3 -3
- package/server/services/ai/toolResult.js +6 -8
- package/server/services/ai/tools.js +62 -95
- package/server/services/commands/router.js +14 -6
- package/server/services/integrations/google/provider.js +20 -2
- package/server/services/integrations/manager.js +79 -8
- package/server/services/integrations/whatsapp/provider.js +23 -1
- package/server/services/manager.js +14 -14
- package/server/services/memory/manager.js +7 -7
- package/server/services/memory/policy.js +1 -1
- package/server/services/messaging/access_policy.js +703 -0
- package/server/services/messaging/access_policy.test.js +228 -0
- package/server/services/messaging/automation.js +32 -95
- package/server/services/messaging/base.js +39 -0
- package/server/services/messaging/discord.js +61 -46
- package/server/services/messaging/formatting_guides.js +0 -4
- package/server/services/messaging/http_platforms.js +178 -15
- package/server/services/messaging/manager.js +136 -71
- package/server/services/messaging/telegram.js +54 -40
- package/server/services/messaging/telnyx.js +43 -14
- package/server/services/messaging/whatsapp.js +27 -0
- package/server/services/tasks/adapters/gmail_message_received.js +36 -0
- package/server/services/tasks/adapters/index.js +10 -0
- package/server/services/tasks/adapters/outlook_email_received.js +38 -0
- package/server/services/tasks/adapters/schedule.js +57 -0
- package/server/services/tasks/adapters/slack_message_received.js +39 -0
- package/server/services/tasks/adapters/teams_message_received.js +39 -0
- package/server/services/tasks/adapters/whatsapp_personal_message_received.js +42 -0
- package/server/services/tasks/integration_runtime.js +260 -0
- package/server/services/tasks/runtime.js +539 -0
- package/server/services/{scheduler/cron_utils.js → tasks/schedule_utils.js} +2 -0
- package/server/services/tasks/security.js +60 -0
- package/server/services/tasks/task_repository.js +162 -0
- package/server/services/tasks/trigger_registry.js +29 -0
- package/server/services/tasks/utils.js +45 -0
- package/server/services/websocket.js +1 -1
- package/server/services/widgets/service.js +37 -25
- package/server/routes/wearable_device.js +0 -147
- package/server/services/messaging/waveshare_wearable.js +0 -40
- package/server/services/scheduler/cron.js +0 -580
- package/server/services/wearables/device_auth.js +0 -228
|
@@ -217,6 +217,27 @@ class ConfigurableHttpPlatform extends BasePlatform {
|
|
|
217
217
|
if (!inboundAllowed(this.config, req)) return { handled: false, status: 403, body: 'Forbidden' };
|
|
218
218
|
const msg = genericMessageFromWebhook(this.name, this.config, req);
|
|
219
219
|
if (!msg) return { handled: true, status: 202, body: 'ignored' };
|
|
220
|
+
const access = this._checkInboundAccess({
|
|
221
|
+
platform: this.name,
|
|
222
|
+
senderId: String(msg.sender || ''),
|
|
223
|
+
chatId: String(msg.chatId || ''),
|
|
224
|
+
isDirect: !msg.isGroup,
|
|
225
|
+
isShared: Boolean(msg.isGroup),
|
|
226
|
+
groupId: msg.isGroup ? String(msg.chatId || '') : '',
|
|
227
|
+
channelId: msg.isGroup ? String(msg.chatId || '') : '',
|
|
228
|
+
serverId: '',
|
|
229
|
+
roomId: msg.isGroup ? String(msg.chatId || '') : '',
|
|
230
|
+
roleIds: [],
|
|
231
|
+
phoneNumber: '',
|
|
232
|
+
wasMentioned: false,
|
|
233
|
+
}, {
|
|
234
|
+
senderName: msg.senderName || null,
|
|
235
|
+
meta: msg.isGroup ? `Chat: ${msg.chatId}` : '',
|
|
236
|
+
groupLabel: String(msg.chatId || ''),
|
|
237
|
+
channelLabel: String(msg.chatId || ''),
|
|
238
|
+
roomLabel: String(msg.chatId || ''),
|
|
239
|
+
});
|
|
240
|
+
if (!access.allowed) return { handled: true, status: 202, body: 'blocked' };
|
|
220
241
|
this.emit('message', msg);
|
|
221
242
|
return { handled: true, status: 200, body: 'OK' };
|
|
222
243
|
}
|
|
@@ -318,7 +339,7 @@ class SlackPlatform extends BasePlatform {
|
|
|
318
339
|
? String(event.text).replace(new RegExp(`<@${this._botUserId}>`, 'g'), '').trim()
|
|
319
340
|
: String(event.text);
|
|
320
341
|
if (!content) return { handled: true, status: 202, body: 'ignored' };
|
|
321
|
-
|
|
342
|
+
const message = {
|
|
322
343
|
platform: 'slack',
|
|
323
344
|
chatId: String(event.channel || 'slack'),
|
|
324
345
|
sender: String(event.user || event.bot_id || 'slack'),
|
|
@@ -332,7 +353,28 @@ class SlackPlatform extends BasePlatform {
|
|
|
332
353
|
timestamp: event.event_ts ? new Date(Number(event.event_ts) * 1000).toISOString() : new Date().toISOString(),
|
|
333
354
|
threadTs: event.thread_ts || null,
|
|
334
355
|
rawMessage: body,
|
|
356
|
+
wasMentioned,
|
|
357
|
+
};
|
|
358
|
+
const access = this._checkInboundAccess({
|
|
359
|
+
platform: 'slack',
|
|
360
|
+
senderId: message.sender,
|
|
361
|
+
chatId: message.chatId,
|
|
362
|
+
isDirect: !isGroup,
|
|
363
|
+
isShared: isGroup,
|
|
364
|
+
groupId: isGroup ? message.chatId : '',
|
|
365
|
+
channelId: isGroup ? message.chatId : '',
|
|
366
|
+
serverId: '',
|
|
367
|
+
roomId: '',
|
|
368
|
+
roleIds: [],
|
|
369
|
+
phoneNumber: '',
|
|
370
|
+
wasMentioned,
|
|
371
|
+
}, {
|
|
372
|
+
senderName: message.senderName,
|
|
373
|
+
meta: isGroup ? `Channel: ${message.chatId}` : '',
|
|
374
|
+
channelLabel: message.chatId,
|
|
335
375
|
});
|
|
376
|
+
if (!access.allowed) return { handled: true, status: 202, body: 'blocked' };
|
|
377
|
+
this.emit('message', message);
|
|
336
378
|
return { handled: true, status: 200, body: 'OK' };
|
|
337
379
|
}
|
|
338
380
|
}
|
|
@@ -345,23 +387,43 @@ class GoogleChatPlatform extends ConfigurableHttpPlatform {
|
|
|
345
387
|
async handleWebhook(req) {
|
|
346
388
|
if (!inboundAllowed(this.config, req)) return { handled: false, status: 403, body: 'Forbidden' };
|
|
347
389
|
const body = req.body || {};
|
|
348
|
-
const
|
|
349
|
-
const content =
|
|
390
|
+
const incoming = body.message || body;
|
|
391
|
+
const content = incoming.argumentText || incoming.text || body.text;
|
|
350
392
|
if (!content) return { handled: true, status: 202, body: 'ignored' };
|
|
351
|
-
|
|
393
|
+
const message = {
|
|
352
394
|
platform: 'google_chat',
|
|
353
|
-
chatId: String(
|
|
354
|
-
sender: String(body.user?.name ||
|
|
355
|
-
senderName: body.user?.displayName ||
|
|
356
|
-
senderDisplayName: body.user?.displayName ||
|
|
357
|
-
senderTag: body.user?.name ||
|
|
395
|
+
chatId: String(incoming.space?.name || body.space?.name || this.config.defaultTo || 'google_chat'),
|
|
396
|
+
sender: String(body.user?.name || incoming.sender?.name || 'google_chat'),
|
|
397
|
+
senderName: body.user?.displayName || incoming.sender?.displayName || null,
|
|
398
|
+
senderDisplayName: body.user?.displayName || incoming.sender?.displayName || null,
|
|
399
|
+
senderTag: body.user?.name || incoming.sender?.name || null,
|
|
358
400
|
content: String(content),
|
|
359
401
|
mediaType: null,
|
|
360
402
|
isGroup: true,
|
|
361
|
-
messageId: String(
|
|
403
|
+
messageId: String(incoming.name || crypto.randomUUID()),
|
|
362
404
|
timestamp: new Date().toISOString(),
|
|
363
405
|
rawMessage: body,
|
|
406
|
+
};
|
|
407
|
+
const access = this._checkInboundAccess({
|
|
408
|
+
platform: 'google_chat',
|
|
409
|
+
senderId: message.sender,
|
|
410
|
+
chatId: message.chatId,
|
|
411
|
+
isDirect: false,
|
|
412
|
+
isShared: true,
|
|
413
|
+
groupId: '',
|
|
414
|
+
channelId: '',
|
|
415
|
+
serverId: '',
|
|
416
|
+
roomId: message.chatId,
|
|
417
|
+
roleIds: [],
|
|
418
|
+
phoneNumber: '',
|
|
419
|
+
wasMentioned: false,
|
|
420
|
+
}, {
|
|
421
|
+
senderName: message.senderName,
|
|
422
|
+
meta: `Space: ${message.chatId}`,
|
|
423
|
+
roomLabel: message.chatId,
|
|
364
424
|
});
|
|
425
|
+
if (!access.allowed) return { handled: true, status: 202, body: 'blocked' };
|
|
426
|
+
this.emit('message', message);
|
|
365
427
|
return { handled: true, status: 200, body: { text: 'Received.' } };
|
|
366
428
|
}
|
|
367
429
|
}
|
|
@@ -376,7 +438,7 @@ class TeamsPlatform extends ConfigurableHttpPlatform {
|
|
|
376
438
|
const body = req.body || {};
|
|
377
439
|
const content = body.text || body.message?.text || body.value?.text;
|
|
378
440
|
if (!content) return { handled: true, status: 202, body: 'ignored' };
|
|
379
|
-
|
|
441
|
+
const message = {
|
|
380
442
|
platform: 'teams',
|
|
381
443
|
chatId: String(body.conversation?.id || body.channelData?.channel?.id || this.config.defaultTo || 'teams'),
|
|
382
444
|
sender: String(body.from?.id || 'teams'),
|
|
@@ -389,7 +451,27 @@ class TeamsPlatform extends ConfigurableHttpPlatform {
|
|
|
389
451
|
messageId: String(body.id || crypto.randomUUID()),
|
|
390
452
|
timestamp: body.timestamp || new Date().toISOString(),
|
|
391
453
|
rawMessage: body,
|
|
454
|
+
};
|
|
455
|
+
const access = this._checkInboundAccess({
|
|
456
|
+
platform: 'teams',
|
|
457
|
+
senderId: message.sender,
|
|
458
|
+
chatId: message.chatId,
|
|
459
|
+
isDirect: false,
|
|
460
|
+
isShared: true,
|
|
461
|
+
groupId: '',
|
|
462
|
+
channelId: message.chatId,
|
|
463
|
+
serverId: '',
|
|
464
|
+
roomId: '',
|
|
465
|
+
roleIds: [],
|
|
466
|
+
phoneNumber: '',
|
|
467
|
+
wasMentioned: false,
|
|
468
|
+
}, {
|
|
469
|
+
senderName: message.senderName,
|
|
470
|
+
meta: `Conversation: ${message.chatId}`,
|
|
471
|
+
channelLabel: message.chatId,
|
|
392
472
|
});
|
|
473
|
+
if (!access.allowed) return { handled: true, status: 202, body: 'blocked' };
|
|
474
|
+
this.emit('message', message);
|
|
393
475
|
return { handled: true, status: 200, body: { type: 'message', text: 'Received.' } };
|
|
394
476
|
}
|
|
395
477
|
}
|
|
@@ -473,7 +555,7 @@ class MatrixPlatform extends BasePlatform {
|
|
|
473
555
|
? String(content).replaceAll(this.userId, '').trim()
|
|
474
556
|
: String(content);
|
|
475
557
|
if (!cleanContent) continue;
|
|
476
|
-
|
|
558
|
+
const message = {
|
|
477
559
|
platform: 'matrix',
|
|
478
560
|
chatId: roomId,
|
|
479
561
|
sender: String(event.sender || roomId),
|
|
@@ -486,7 +568,27 @@ class MatrixPlatform extends BasePlatform {
|
|
|
486
568
|
messageId: String(event.event_id || crypto.randomUUID()),
|
|
487
569
|
timestamp: event.origin_server_ts ? new Date(event.origin_server_ts).toISOString() : new Date().toISOString(),
|
|
488
570
|
rawMessage: event,
|
|
571
|
+
};
|
|
572
|
+
const access = this._checkInboundAccess({
|
|
573
|
+
platform: 'matrix',
|
|
574
|
+
senderId: message.sender,
|
|
575
|
+
chatId: message.chatId,
|
|
576
|
+
isDirect: false,
|
|
577
|
+
isShared: true,
|
|
578
|
+
groupId: '',
|
|
579
|
+
channelId: '',
|
|
580
|
+
serverId: '',
|
|
581
|
+
roomId: roomId,
|
|
582
|
+
roleIds: [],
|
|
583
|
+
phoneNumber: '',
|
|
584
|
+
wasMentioned: this.userId ? String(content).includes(this.userId) : false,
|
|
585
|
+
}, {
|
|
586
|
+
senderName: message.senderName,
|
|
587
|
+
meta: `Room: ${roomId}`,
|
|
588
|
+
roomLabel: roomId,
|
|
489
589
|
});
|
|
590
|
+
if (!access.allowed) continue;
|
|
591
|
+
this.emit('message', message);
|
|
490
592
|
}
|
|
491
593
|
}
|
|
492
594
|
}
|
|
@@ -574,7 +676,7 @@ class SignalPlatform extends ConfigurableHttpPlatform {
|
|
|
574
676
|
const dataMessage = envelope.dataMessage || {};
|
|
575
677
|
const content = dataMessage.message || '';
|
|
576
678
|
if (!content) continue;
|
|
577
|
-
|
|
679
|
+
const message = {
|
|
578
680
|
platform: 'signal',
|
|
579
681
|
chatId: String(envelope.sourceNumber || envelope.source || 'signal'),
|
|
580
682
|
sender: String(envelope.sourceNumber || envelope.source || 'signal'),
|
|
@@ -587,7 +689,27 @@ class SignalPlatform extends ConfigurableHttpPlatform {
|
|
|
587
689
|
messageId: String(envelope.timestamp || crypto.randomUUID()),
|
|
588
690
|
timestamp: envelope.timestamp ? new Date(Number(envelope.timestamp)).toISOString() : new Date().toISOString(),
|
|
589
691
|
rawMessage: item,
|
|
692
|
+
};
|
|
693
|
+
const access = this._checkInboundAccess({
|
|
694
|
+
platform: 'signal',
|
|
695
|
+
senderId: message.sender,
|
|
696
|
+
chatId: message.chatId,
|
|
697
|
+
isDirect: !message.isGroup,
|
|
698
|
+
isShared: message.isGroup,
|
|
699
|
+
groupId: message.isGroup ? message.chatId : '',
|
|
700
|
+
channelId: '',
|
|
701
|
+
serverId: '',
|
|
702
|
+
roomId: '',
|
|
703
|
+
roleIds: [],
|
|
704
|
+
phoneNumber: message.sender,
|
|
705
|
+
wasMentioned: false,
|
|
706
|
+
}, {
|
|
707
|
+
senderName: message.senderName,
|
|
708
|
+
meta: message.isGroup ? `Group: ${message.chatId}` : '',
|
|
709
|
+
groupLabel: message.chatId,
|
|
590
710
|
});
|
|
711
|
+
if (!access.allowed) continue;
|
|
712
|
+
this.emit('message', message);
|
|
591
713
|
}
|
|
592
714
|
}
|
|
593
715
|
|
|
@@ -640,7 +762,7 @@ class LinePlatform extends ConfigurableHttpPlatform {
|
|
|
640
762
|
for (const event of events) {
|
|
641
763
|
const content = event.message?.text || '';
|
|
642
764
|
if (!content) continue;
|
|
643
|
-
|
|
765
|
+
const message = {
|
|
644
766
|
platform: 'line',
|
|
645
767
|
chatId: String(event.source?.groupId || event.source?.roomId || event.source?.userId || 'line'),
|
|
646
768
|
sender: String(event.source?.userId || 'line'),
|
|
@@ -652,7 +774,28 @@ class LinePlatform extends ConfigurableHttpPlatform {
|
|
|
652
774
|
messageId: String(event.message?.id || event.webhookEventId || crypto.randomUUID()),
|
|
653
775
|
timestamp: event.timestamp ? new Date(Number(event.timestamp)).toISOString() : new Date().toISOString(),
|
|
654
776
|
rawMessage: event,
|
|
777
|
+
};
|
|
778
|
+
const access = this._checkInboundAccess({
|
|
779
|
+
platform: 'line',
|
|
780
|
+
senderId: message.sender,
|
|
781
|
+
chatId: message.chatId,
|
|
782
|
+
isDirect: !message.isGroup,
|
|
783
|
+
isShared: message.isGroup,
|
|
784
|
+
groupId: event.source?.groupId ? String(event.source.groupId) : '',
|
|
785
|
+
channelId: '',
|
|
786
|
+
serverId: '',
|
|
787
|
+
roomId: event.source?.roomId ? String(event.source.roomId) : '',
|
|
788
|
+
roleIds: [],
|
|
789
|
+
phoneNumber: '',
|
|
790
|
+
wasMentioned: false,
|
|
791
|
+
}, {
|
|
792
|
+
senderName: message.senderName,
|
|
793
|
+
meta: event.source?.groupId ? `Group: ${event.source.groupId}` : (event.source?.roomId ? `Room: ${event.source.roomId}` : ''),
|
|
794
|
+
groupLabel: event.source?.groupId ? String(event.source.groupId) : '',
|
|
795
|
+
roomLabel: event.source?.roomId ? String(event.source.roomId) : '',
|
|
655
796
|
});
|
|
797
|
+
if (!access.allowed) continue;
|
|
798
|
+
this.emit('message', message);
|
|
656
799
|
}
|
|
657
800
|
return { handled: true, status: 200, body: 'OK' };
|
|
658
801
|
}
|
|
@@ -795,7 +938,7 @@ class IrcPlatform extends BasePlatform {
|
|
|
795
938
|
? content.replace(new RegExp(`(^|\\s)${this.nick.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[:,]?\\s*`, 'i'), ' ').trim()
|
|
796
939
|
: content;
|
|
797
940
|
if (!cleanContent) continue;
|
|
798
|
-
|
|
941
|
+
const message = {
|
|
799
942
|
platform: this.name,
|
|
800
943
|
chatId: target,
|
|
801
944
|
sender: nick,
|
|
@@ -807,7 +950,27 @@ class IrcPlatform extends BasePlatform {
|
|
|
807
950
|
isGroup,
|
|
808
951
|
messageId: crypto.randomUUID(),
|
|
809
952
|
timestamp: new Date().toISOString(),
|
|
953
|
+
};
|
|
954
|
+
const access = this._checkInboundAccess({
|
|
955
|
+
platform: this.name,
|
|
956
|
+
senderId: nick,
|
|
957
|
+
chatId: target,
|
|
958
|
+
isDirect: !isGroup,
|
|
959
|
+
isShared: isGroup,
|
|
960
|
+
groupId: '',
|
|
961
|
+
channelId: isGroup ? target : '',
|
|
962
|
+
serverId: '',
|
|
963
|
+
roomId: '',
|
|
964
|
+
roleIds: [],
|
|
965
|
+
phoneNumber: '',
|
|
966
|
+
wasMentioned: !isGroup || new RegExp(`(^|\\s)${this.nick.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}[:,]?\\b`, 'i').test(content),
|
|
967
|
+
}, {
|
|
968
|
+
senderName: nick,
|
|
969
|
+
meta: isGroup ? `Channel: ${target}` : '',
|
|
970
|
+
channelLabel: target,
|
|
810
971
|
});
|
|
972
|
+
if (!access.allowed) continue;
|
|
973
|
+
this.emit('message', message);
|
|
811
974
|
}
|
|
812
975
|
}
|
|
813
976
|
|
|
@@ -9,7 +9,6 @@ const { WhatsAppPlatform } = require('./whatsapp');
|
|
|
9
9
|
const { TelnyxVoicePlatform } = require('./telnyx');
|
|
10
10
|
const { DiscordPlatform } = require('./discord');
|
|
11
11
|
const { TelegramPlatform } = require('./telegram');
|
|
12
|
-
const { WaveshareWearablePlatform } = require('./waveshare_wearable');
|
|
13
12
|
const {
|
|
14
13
|
SlackPlatform,
|
|
15
14
|
GoogleChatPlatform,
|
|
@@ -23,29 +22,17 @@ const {
|
|
|
23
22
|
createGenericPlatformClass,
|
|
24
23
|
} = require('./http_platforms');
|
|
25
24
|
const { normalizeOutgoingMessageForPlatform } = require('./formatting_guides');
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'line',
|
|
38
|
-
'mattermost',
|
|
39
|
-
'nextcloud_talk',
|
|
40
|
-
'nostr',
|
|
41
|
-
'synology_chat',
|
|
42
|
-
'tlon',
|
|
43
|
-
'twitch',
|
|
44
|
-
'zalo',
|
|
45
|
-
'zalo_personal',
|
|
46
|
-
'wechat',
|
|
47
|
-
'webchat',
|
|
48
|
-
]);
|
|
25
|
+
const {
|
|
26
|
+
accessPolicyKey,
|
|
27
|
+
legacyWhitelistKey,
|
|
28
|
+
getPlatformAccessCapabilities,
|
|
29
|
+
normalizeAccessPolicy,
|
|
30
|
+
migrateLegacyWhitelist,
|
|
31
|
+
parseStoredAccessPolicy,
|
|
32
|
+
evaluateAccessPolicy,
|
|
33
|
+
summarizeAccessPolicy,
|
|
34
|
+
classifyRecentTarget,
|
|
35
|
+
} = require('./access_policy');
|
|
49
36
|
|
|
50
37
|
const LEGACY_WHATSAPP_AUTH_DIR = path.join(DATA_DIR, 'whatsapp-auth');
|
|
51
38
|
|
|
@@ -71,6 +58,7 @@ class MessagingManager extends EventEmitter {
|
|
|
71
58
|
this.io = io;
|
|
72
59
|
this.voiceRuntimeManager = options.voiceRuntimeManager || null;
|
|
73
60
|
this.platforms = new Map();
|
|
61
|
+
this.accessSuggestions = new Map();
|
|
74
62
|
this.messageHandlers = [];
|
|
75
63
|
this.isShuttingDown = false;
|
|
76
64
|
this.platformTypes = {
|
|
@@ -89,7 +77,6 @@ class MessagingManager extends EventEmitter {
|
|
|
89
77
|
feishu: createGenericPlatformClass('feishu'),
|
|
90
78
|
line: LinePlatform,
|
|
91
79
|
mattermost: MattermostPlatform,
|
|
92
|
-
waveshare_wearable: WaveshareWearablePlatform,
|
|
93
80
|
nextcloud_talk: createGenericPlatformClass('nextcloud_talk'),
|
|
94
81
|
nostr: createGenericPlatformClass('nostr'),
|
|
95
82
|
synology_chat: createGenericPlatformClass('synology_chat'),
|
|
@@ -178,6 +165,41 @@ class MessagingManager extends EventEmitter {
|
|
|
178
165
|
.get(userId, key);
|
|
179
166
|
}
|
|
180
167
|
|
|
168
|
+
_upsertSetting(userId, agentId, key, value) {
|
|
169
|
+
db.prepare(
|
|
170
|
+
`INSERT INTO agent_settings (user_id, agent_id, key, value)
|
|
171
|
+
VALUES (?, ?, ?, ?)
|
|
172
|
+
ON CONFLICT(user_id, agent_id, key) DO UPDATE SET value = excluded.value`
|
|
173
|
+
).run(userId, agentId, key, JSON.stringify(value));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_accessSuggestionKey(userId, agentId, platformName) {
|
|
177
|
+
return `${userId}:${agentId}:${platformName}:access-suggestions`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_rememberAccessSuggestions(userId, agentId, platformName, suggestions = []) {
|
|
181
|
+
if (!Array.isArray(suggestions) || suggestions.length === 0) return;
|
|
182
|
+
const key = this._accessSuggestionKey(userId, agentId, platformName);
|
|
183
|
+
const existing = this.accessSuggestions.get(key) || [];
|
|
184
|
+
const merged = [...suggestions, ...existing].filter((item) => item && item.rule && item.bucket);
|
|
185
|
+
const unique = [];
|
|
186
|
+
const seen = new Set();
|
|
187
|
+
for (const item of merged) {
|
|
188
|
+
const id = `${item.bucket}:${item.rule.scope}:${item.rule.value}`;
|
|
189
|
+
if (seen.has(id)) continue;
|
|
190
|
+
seen.add(id);
|
|
191
|
+
unique.push(item);
|
|
192
|
+
if (unique.length >= 24) break;
|
|
193
|
+
}
|
|
194
|
+
this.accessSuggestions.set(key, unique);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
_loadAccessPolicy(userId, agentId, platformName) {
|
|
198
|
+
const policyRow = this._setting(userId, agentId, accessPolicyKey(platformName));
|
|
199
|
+
const legacyRow = this._setting(userId, agentId, legacyWhitelistKey(platformName));
|
|
200
|
+
return parseStoredAccessPolicy(platformName, policyRow?.value, legacyRow?.value);
|
|
201
|
+
}
|
|
202
|
+
|
|
181
203
|
_scopedPlatformAuthDir(userId, agentId, platformName) {
|
|
182
204
|
return path.join(
|
|
183
205
|
AGENT_DATA_DIR,
|
|
@@ -251,6 +273,7 @@ class MessagingManager extends EventEmitter {
|
|
|
251
273
|
config = { ...(config || {}) };
|
|
252
274
|
config.userId = userId;
|
|
253
275
|
config.agentId = agentId;
|
|
276
|
+
config.accessPolicy = this._loadAccessPolicy(userId, agentId, platformName);
|
|
254
277
|
const PlatformClass = this.platformTypes[platformName];
|
|
255
278
|
if (!PlatformClass) throw new Error(`Unknown platform: ${platformName}`);
|
|
256
279
|
|
|
@@ -270,10 +293,6 @@ class MessagingManager extends EventEmitter {
|
|
|
270
293
|
}
|
|
271
294
|
};
|
|
272
295
|
|
|
273
|
-
const wlRow = this._setting(userId, agentId, 'platform_whitelist_telnyx');
|
|
274
|
-
if (wlRow) {
|
|
275
|
-
try { config.allowedNumbers = JSON.parse(wlRow.value); } catch { /* ignore */ }
|
|
276
|
-
}
|
|
277
296
|
const secretRow = this._setting(userId, agentId, 'platform_voice_secret_telnyx');
|
|
278
297
|
if (secretRow) {
|
|
279
298
|
try { config.voiceSecret = JSON.parse(secretRow.value); } catch { config.voiceSecret = secretRow.value; }
|
|
@@ -303,32 +322,6 @@ class MessagingManager extends EventEmitter {
|
|
|
303
322
|
config.voiceRuntimeManager = this.voiceRuntimeManager || null;
|
|
304
323
|
}
|
|
305
324
|
|
|
306
|
-
// Inject saved allowlists for platforms that enforce access in the adapter.
|
|
307
|
-
if (platformName === 'discord') {
|
|
308
|
-
const wlRow = this._setting(userId, agentId, 'platform_whitelist_discord');
|
|
309
|
-
if (wlRow) {
|
|
310
|
-
try { config.allowedIds = JSON.parse(wlRow.value); } catch { /* ignore */ }
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// For Telegram, inject saved allowedIds whitelist
|
|
315
|
-
if (platformName === 'telegram') {
|
|
316
|
-
const wlRow = this._setting(userId, agentId, 'platform_whitelist_telegram');
|
|
317
|
-
if (wlRow) {
|
|
318
|
-
try { config.allowedIds = JSON.parse(wlRow.value); } catch { /* ignore */ }
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (GENERIC_ALLOWLIST_PLATFORMS.has(platformName)) {
|
|
323
|
-
const wlRow = this._setting(userId, agentId, `platform_whitelist_${platformName}`);
|
|
324
|
-
if (wlRow) {
|
|
325
|
-
try {
|
|
326
|
-
const parsed = JSON.parse(wlRow.value);
|
|
327
|
-
if (Array.isArray(parsed)) config.allowedIds = parsed;
|
|
328
|
-
} catch { /* ignore */ }
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
325
|
const storedConfig = JSON.stringify(this._persistableConfig(config) || {});
|
|
333
326
|
|
|
334
327
|
const key = this._key(userId, agentId, platformName);
|
|
@@ -375,22 +368,26 @@ class MessagingManager extends EventEmitter {
|
|
|
375
368
|
|
|
376
369
|
// Telnyx-specific: blocked inbound caller notification
|
|
377
370
|
platform.on('blocked_caller', (info) => {
|
|
371
|
+
this._rememberAccessSuggestions(userId, agentId, platformName, info?.suggestions || []);
|
|
378
372
|
this.io.to(`user:${userId}`).emit('messaging:blocked_sender', {
|
|
379
373
|
platform: platformName,
|
|
380
374
|
sender: info.caller,
|
|
381
375
|
chatId: info.ccId,
|
|
382
|
-
senderName: null
|
|
376
|
+
senderName: null,
|
|
377
|
+
meta: info.meta || '',
|
|
378
|
+
suggestions: info.suggestions || null,
|
|
383
379
|
});
|
|
384
380
|
});
|
|
385
381
|
|
|
386
|
-
//
|
|
382
|
+
// Adapter-level blocked sender notification with suggestions
|
|
387
383
|
platform.on('blocked_sender', (info) => {
|
|
384
|
+
this._rememberAccessSuggestions(userId, agentId, platformName, info?.suggestions || []);
|
|
388
385
|
this.io.to(`user:${userId}`).emit('messaging:blocked_sender', {
|
|
389
386
|
platform: platformName,
|
|
390
387
|
sender: info.sender,
|
|
391
388
|
chatId: info.chatId,
|
|
392
389
|
senderName: info.senderName || null,
|
|
393
|
-
meta: info.guildName ? `Server: ${info.guildName}` : (info.groupName ? `Group: ${info.groupName}` : null),
|
|
390
|
+
meta: info.meta || (info.guildName ? `Server: ${info.guildName}` : (info.groupName ? `Group: ${info.groupName}` : null)),
|
|
394
391
|
suggestions: info.suggestions || null,
|
|
395
392
|
});
|
|
396
393
|
});
|
|
@@ -681,13 +678,88 @@ class MessagingManager extends EventEmitter {
|
|
|
681
678
|
};
|
|
682
679
|
}
|
|
683
680
|
|
|
681
|
+
getAccessPolicy(userId, platformName, options = {}) {
|
|
682
|
+
const agentId = this._agentId(userId, options);
|
|
683
|
+
return this._loadAccessPolicy(userId, agentId, platformName);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
setAccessPolicy(userId, platformName, policy, options = {}) {
|
|
687
|
+
const agentId = this._agentId(userId, options);
|
|
688
|
+
const normalized = normalizeAccessPolicy(platformName, policy);
|
|
689
|
+
this._upsertSetting(userId, agentId, accessPolicyKey(platformName), normalized);
|
|
690
|
+
const key = this._key(userId, agentId, platformName);
|
|
691
|
+
const platform = this.platforms.get(key);
|
|
692
|
+
if (platform?.setAccessPolicy) {
|
|
693
|
+
platform.setAccessPolicy(normalized);
|
|
694
|
+
}
|
|
695
|
+
return normalized;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
evaluateAccess(userId, platformName, context, options = {}) {
|
|
699
|
+
const agentId = this._agentId(userId, options);
|
|
700
|
+
const key = this._key(userId, agentId, platformName);
|
|
701
|
+
const platform = this.platforms.get(key);
|
|
702
|
+
if (platform?.evaluateAccess) {
|
|
703
|
+
return platform.evaluateAccess(context);
|
|
704
|
+
}
|
|
705
|
+
return evaluateAccessPolicy(
|
|
706
|
+
this._loadAccessPolicy(userId, agentId, platformName),
|
|
707
|
+
context,
|
|
708
|
+
platformName,
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async getAccessCatalog(userId, platformName, options = {}) {
|
|
713
|
+
const agentId = this._agentId(userId, options);
|
|
714
|
+
const key = this._key(userId, agentId, platformName);
|
|
715
|
+
const platform = this.platforms.get(key);
|
|
716
|
+
let discoveredTargets = [];
|
|
717
|
+
if (platform?.listAccessTargets) {
|
|
718
|
+
discoveredTargets = await Promise.resolve(platform.listAccessTargets()).catch(() => []);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const recentRows = db.prepare(
|
|
722
|
+
`SELECT platform_chat_id, metadata
|
|
723
|
+
FROM messages
|
|
724
|
+
WHERE user_id = ? AND agent_id = ? AND platform = ? AND platform_chat_id IS NOT NULL
|
|
725
|
+
ORDER BY id DESC
|
|
726
|
+
LIMIT 40`
|
|
727
|
+
).all(userId, agentId, platformName);
|
|
728
|
+
const recentTargets = recentRows
|
|
729
|
+
.map((row) => {
|
|
730
|
+
let metadata = {};
|
|
731
|
+
try {
|
|
732
|
+
metadata = row.metadata ? JSON.parse(row.metadata) : {};
|
|
733
|
+
} catch {
|
|
734
|
+
metadata = {};
|
|
735
|
+
}
|
|
736
|
+
return classifyRecentTarget(platformName, { ...row, metadata });
|
|
737
|
+
})
|
|
738
|
+
.filter(Boolean);
|
|
739
|
+
|
|
740
|
+
const seen = new Set();
|
|
741
|
+
const unique = (items) => items.filter((item) => {
|
|
742
|
+
const keyValue = `${item.bucket}:${item.scope}:${item.value}`;
|
|
743
|
+
if (seen.has(keyValue)) return false;
|
|
744
|
+
seen.add(keyValue);
|
|
745
|
+
return true;
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
return {
|
|
749
|
+
capabilities: getPlatformAccessCapabilities(platformName),
|
|
750
|
+
discoveredTargets: unique([...(Array.isArray(discoveredTargets) ? discoveredTargets : []), ...recentTargets]),
|
|
751
|
+
suggestedTargets: unique(this.accessSuggestions.get(this._accessSuggestionKey(userId, agentId, platformName)) || []),
|
|
752
|
+
policy: this._loadAccessPolicy(userId, agentId, platformName),
|
|
753
|
+
summary: summarizeAccessPolicy(platformName, this._loadAccessPolicy(userId, agentId, platformName)),
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
684
757
|
/**
|
|
685
758
|
* Update the allowed-numbers list on a live Telnyx platform instance.
|
|
686
759
|
*/
|
|
687
760
|
updateTelnyxAllowedNumbers(userId, numbers, options = {}) {
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
if (platform?.setAllowedNumbers) platform.setAllowedNumbers(numbers);
|
|
761
|
+
const migrated = migrateLegacyWhitelist('telnyx', numbers);
|
|
762
|
+
return this.setAccessPolicy(userId, 'telnyx', migrated, options);
|
|
691
763
|
}
|
|
692
764
|
|
|
693
765
|
/**
|
|
@@ -712,10 +784,7 @@ class MessagingManager extends EventEmitter {
|
|
|
712
784
|
* Accepts prefixed strings: "user:ID", "guild:ID", "channel:ID"
|
|
713
785
|
*/
|
|
714
786
|
updateDiscordAllowedIds(userId, ids, options = {}) {
|
|
715
|
-
|
|
716
|
-
const platform = this.platforms.get(key);
|
|
717
|
-
if (platform?.setAllowedEntries) platform.setAllowedEntries(ids);
|
|
718
|
-
else if (platform?.setAllowedIds) platform.setAllowedIds(ids); // legacy fallback
|
|
787
|
+
return this.setAccessPolicy(userId, 'discord', migrateLegacyWhitelist('discord', ids), options);
|
|
719
788
|
}
|
|
720
789
|
|
|
721
790
|
/**
|
|
@@ -723,15 +792,11 @@ class MessagingManager extends EventEmitter {
|
|
|
723
792
|
* Accepts prefixed strings: "user:ID", "group:ID"
|
|
724
793
|
*/
|
|
725
794
|
updateTelegramAllowedIds(userId, ids, options = {}) {
|
|
726
|
-
|
|
727
|
-
const platform = this.platforms.get(key);
|
|
728
|
-
if (platform?.setAllowedEntries) platform.setAllowedEntries(ids);
|
|
795
|
+
return this.setAccessPolicy(userId, 'telegram', migrateLegacyWhitelist('telegram', ids), options);
|
|
729
796
|
}
|
|
730
797
|
|
|
731
798
|
updateAllowedEntries(userId, platformName, ids, options = {}) {
|
|
732
|
-
|
|
733
|
-
const platform = this.platforms.get(key);
|
|
734
|
-
if (platform?.setAllowedEntries) platform.setAllowedEntries(ids);
|
|
799
|
+
return this.setAccessPolicy(userId, platformName, migrateLegacyWhitelist(platformName, ids), options);
|
|
735
800
|
}
|
|
736
801
|
}
|
|
737
802
|
|