neoagent 2.2.1-beta.6 → 2.2.1-beta.7
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 +6 -7
- package/docs/integrations.md +1 -1
- package/docs/operations.md +1 -1
- package/docs/why-neoagent.md +2 -2
- package/package.json +1 -1
- 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 +65118 -64805
- 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/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/formatting_guides.js +0 -4
- package/server/services/messaging/manager.js +0 -2
- 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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
ensureOwnedIntegrationConnection,
|
|
5
|
+
normalizeBoolean,
|
|
6
|
+
normalizeTrimmedText,
|
|
7
|
+
} = require('../security');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
type: 'outlook_email_received',
|
|
11
|
+
label: 'Outlook Email Received',
|
|
12
|
+
providerKey: 'microsoft_365',
|
|
13
|
+
appKey: 'outlook',
|
|
14
|
+
async validateConfig(config = {}, context = {}) {
|
|
15
|
+
const connection = ensureOwnedIntegrationConnection(context.integrationManager, {
|
|
16
|
+
userId: context.userId,
|
|
17
|
+
agentId: context.agentId,
|
|
18
|
+
connectionId: config.connectionId || config.connection_id,
|
|
19
|
+
providerKey: 'microsoft_365',
|
|
20
|
+
appKey: 'outlook',
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
connectionId: connection.id,
|
|
24
|
+
accountEmail: connection.account_email || null,
|
|
25
|
+
folderId: normalizeTrimmedText(config.folderId || config.folder_id, 160),
|
|
26
|
+
query: normalizeTrimmedText(config.query, 500),
|
|
27
|
+
unreadOnly: normalizeBoolean(config.unreadOnly ?? config.unread_only, false),
|
|
28
|
+
};
|
|
29
|
+
},
|
|
30
|
+
summarize(config = {}) {
|
|
31
|
+
const parts = ['Outlook'];
|
|
32
|
+
if (config.accountEmail) parts.push(config.accountEmail);
|
|
33
|
+
if (config.folderId) parts.push(`folder: ${config.folderId}`);
|
|
34
|
+
if (config.query) parts.push(`query: ${config.query}`);
|
|
35
|
+
if (config.unreadOnly) parts.push('unread only');
|
|
36
|
+
return parts.join(' · ');
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const cron = require('node-cron');
|
|
4
|
+
const { findNextRun } = require('../schedule_utils');
|
|
5
|
+
|
|
6
|
+
function normalizeRunAt(value) {
|
|
7
|
+
if (!value) return null;
|
|
8
|
+
const date = new Date(value);
|
|
9
|
+
if (Number.isNaN(date.getTime())) {
|
|
10
|
+
throw new Error('A valid runAt datetime is required.');
|
|
11
|
+
}
|
|
12
|
+
return date.toISOString();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
type: 'schedule',
|
|
17
|
+
label: 'Schedule',
|
|
18
|
+
async validateConfig(config = {}) {
|
|
19
|
+
const mode = String(config.mode || '').trim() || ((config.runAt || config.run_at) ? 'one_time' : 'recurring');
|
|
20
|
+
if (!['recurring', 'one_time'].includes(mode)) {
|
|
21
|
+
throw new Error('Schedule trigger mode must be "recurring" or "one_time".');
|
|
22
|
+
}
|
|
23
|
+
if (mode === 'one_time') {
|
|
24
|
+
const runAt = normalizeRunAt(config.runAt || config.run_at);
|
|
25
|
+
if (!runAt) {
|
|
26
|
+
throw new Error('one_time schedule requires runAt');
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
mode,
|
|
30
|
+
runAt,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cronExpression = String(config.cronExpression || config.cron_expression || '').trim();
|
|
35
|
+
if (!cronExpression || !cron.validate(cronExpression)) {
|
|
36
|
+
throw new Error('A valid cron expression is required.');
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
mode,
|
|
40
|
+
cronExpression,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
summarize(config = {}) {
|
|
44
|
+
if (config.mode === 'one_time') {
|
|
45
|
+
return config.runAt ? `One-time at ${config.runAt}` : 'One-time';
|
|
46
|
+
}
|
|
47
|
+
return String(config.cronExpression || '').trim() || 'Recurring schedule';
|
|
48
|
+
},
|
|
49
|
+
nextRun(config = {}) {
|
|
50
|
+
if (config.mode === 'one_time') return config.runAt || null;
|
|
51
|
+
try {
|
|
52
|
+
return findNextRun(config.cronExpression)?.toISOString() || null;
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
ensureOwnedIntegrationConnection,
|
|
5
|
+
normalizeTrimmedText,
|
|
6
|
+
} = require('../security');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
type: 'slack_message_received',
|
|
10
|
+
label: 'Slack Message Received',
|
|
11
|
+
providerKey: 'slack',
|
|
12
|
+
appKey: 'slack',
|
|
13
|
+
async validateConfig(config = {}, context = {}) {
|
|
14
|
+
const connection = ensureOwnedIntegrationConnection(context.integrationManager, {
|
|
15
|
+
userId: context.userId,
|
|
16
|
+
agentId: context.agentId,
|
|
17
|
+
connectionId: config.connectionId || config.connection_id,
|
|
18
|
+
providerKey: 'slack',
|
|
19
|
+
appKey: 'slack',
|
|
20
|
+
});
|
|
21
|
+
const channel = normalizeTrimmedText(config.channel, 160);
|
|
22
|
+
if (!channel) {
|
|
23
|
+
throw new Error('Slack channel is required.');
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
connectionId: connection.id,
|
|
27
|
+
accountEmail: connection.account_email || null,
|
|
28
|
+
channel,
|
|
29
|
+
sender: normalizeTrimmedText(config.sender, 160),
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
summarize(config = {}) {
|
|
33
|
+
const parts = ['Slack'];
|
|
34
|
+
if (config.accountEmail) parts.push(config.accountEmail);
|
|
35
|
+
if (config.channel) parts.push(config.channel);
|
|
36
|
+
if (config.sender) parts.push(`sender: ${config.sender}`);
|
|
37
|
+
return parts.join(' · ');
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
ensureOwnedIntegrationConnection,
|
|
5
|
+
normalizeTrimmedText,
|
|
6
|
+
} = require('../security');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
type: 'teams_message_received',
|
|
10
|
+
label: 'Teams Message Received',
|
|
11
|
+
providerKey: 'microsoft_365',
|
|
12
|
+
appKey: 'teams',
|
|
13
|
+
async validateConfig(config = {}, context = {}) {
|
|
14
|
+
const connection = ensureOwnedIntegrationConnection(context.integrationManager, {
|
|
15
|
+
userId: context.userId,
|
|
16
|
+
agentId: context.agentId,
|
|
17
|
+
connectionId: config.connectionId || config.connection_id,
|
|
18
|
+
providerKey: 'microsoft_365',
|
|
19
|
+
appKey: 'teams',
|
|
20
|
+
});
|
|
21
|
+
const chatId = normalizeTrimmedText(config.chatId || config.chat_id, 200);
|
|
22
|
+
if (!chatId) {
|
|
23
|
+
throw new Error('Teams chat ID is required.');
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
connectionId: connection.id,
|
|
27
|
+
accountEmail: connection.account_email || null,
|
|
28
|
+
chatId,
|
|
29
|
+
sender: normalizeTrimmedText(config.sender, 200),
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
summarize(config = {}) {
|
|
33
|
+
const parts = ['Teams'];
|
|
34
|
+
if (config.accountEmail) parts.push(config.accountEmail);
|
|
35
|
+
if (config.chatId) parts.push(`chat: ${config.chatId}`);
|
|
36
|
+
if (config.sender) parts.push(`sender: ${config.sender}`);
|
|
37
|
+
return parts.join(' · ');
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
ensureOwnedIntegrationConnection,
|
|
5
|
+
normalizeBoolean,
|
|
6
|
+
normalizeTrimmedText,
|
|
7
|
+
} = require('../security');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
type: 'whatsapp_personal_message_received',
|
|
11
|
+
label: 'WhatsApp Personal Message Received',
|
|
12
|
+
providerKey: 'whatsapp_personal',
|
|
13
|
+
appKey: 'personal',
|
|
14
|
+
async validateConfig(config = {}, context = {}) {
|
|
15
|
+
const connection = ensureOwnedIntegrationConnection(context.integrationManager, {
|
|
16
|
+
userId: context.userId,
|
|
17
|
+
agentId: context.agentId,
|
|
18
|
+
connectionId: config.connectionId || config.connection_id,
|
|
19
|
+
providerKey: 'whatsapp_personal',
|
|
20
|
+
appKey: 'personal',
|
|
21
|
+
});
|
|
22
|
+
const chatId = normalizeTrimmedText(config.chatId || config.chat_id, 200);
|
|
23
|
+
if (!chatId) {
|
|
24
|
+
throw new Error('WhatsApp chat ID is required.');
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
connectionId: connection.id,
|
|
28
|
+
accountEmail: connection.account_email || null,
|
|
29
|
+
chatId,
|
|
30
|
+
sender: normalizeTrimmedText(config.sender, 200),
|
|
31
|
+
ignoreGroups: normalizeBoolean(config.ignoreGroups ?? config.ignore_groups, false),
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
summarize(config = {}) {
|
|
35
|
+
const parts = ['WhatsApp Personal'];
|
|
36
|
+
if (config.accountEmail) parts.push(config.accountEmail);
|
|
37
|
+
if (config.chatId) parts.push(config.chatId);
|
|
38
|
+
if (config.sender) parts.push(`sender: ${config.sender}`);
|
|
39
|
+
if (config.ignoreGroups) parts.push('ignore groups');
|
|
40
|
+
return parts.join(' · ');
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { resolveAgentId } = require('../agents/manager');
|
|
4
|
+
const { normalizeJsonObject } = require('./utils');
|
|
5
|
+
|
|
6
|
+
const POLLED_TRIGGER_TYPES = Object.freeze([
|
|
7
|
+
'gmail_message_received',
|
|
8
|
+
'outlook_email_received',
|
|
9
|
+
'slack_message_received',
|
|
10
|
+
'teams_message_received',
|
|
11
|
+
'whatsapp_personal_message_received',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
function sortByTimestamp(left, right) {
|
|
15
|
+
return String(left.timestamp).localeCompare(String(right.timestamp));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function fetchTriggerRows({ integrationManager, userId, agentId, triggerType, config }) {
|
|
19
|
+
if (!integrationManager) return [];
|
|
20
|
+
const scopedAgentId = resolveAgentId(userId, agentId);
|
|
21
|
+
const connectionArg = {
|
|
22
|
+
connection_id: config.connectionId,
|
|
23
|
+
account_email: config.accountEmail || undefined,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (triggerType === 'gmail_message_received') {
|
|
27
|
+
const queryParts = [];
|
|
28
|
+
if (config.query) queryParts.push(config.query);
|
|
29
|
+
if (config.unreadOnly) queryParts.push('is:unread');
|
|
30
|
+
const result = await integrationManager.executeTool(userId, 'google_workspace_gmail_api_request', {
|
|
31
|
+
...connectionArg,
|
|
32
|
+
method: 'GET',
|
|
33
|
+
path: '/gmail/v1/users/me/messages',
|
|
34
|
+
query: {
|
|
35
|
+
maxResults: 20,
|
|
36
|
+
q: queryParts.join(' ').trim() || undefined,
|
|
37
|
+
},
|
|
38
|
+
}, scopedAgentId);
|
|
39
|
+
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
40
|
+
return messages
|
|
41
|
+
.map((item) => ({
|
|
42
|
+
fingerprint: `gmail:${config.connectionId}:${item.id}`,
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
context: { triggerEvent: { provider: 'gmail', messageId: item.id, threadId: item.threadId || null } },
|
|
45
|
+
}))
|
|
46
|
+
.sort((left, right) => left.fingerprint.localeCompare(right.fingerprint));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (triggerType === 'outlook_email_received') {
|
|
50
|
+
const filters = [];
|
|
51
|
+
const escapedQuery = String(config.query || '').replace(/"/g, '\\"');
|
|
52
|
+
if (config.unreadOnly) filters.push('isRead eq false');
|
|
53
|
+
const result = await integrationManager.executeTool(userId, 'microsoft_365_outlook_graph_request', {
|
|
54
|
+
...connectionArg,
|
|
55
|
+
method: 'GET',
|
|
56
|
+
path: config.folderId
|
|
57
|
+
? `/v1.0/me/mailFolders/${encodeURIComponent(config.folderId)}/messages`
|
|
58
|
+
: '/v1.0/me/messages',
|
|
59
|
+
query: {
|
|
60
|
+
'$top': 20,
|
|
61
|
+
...(filters.length ? { '$filter': filters.join(' and ') } : {}),
|
|
62
|
+
...(escapedQuery ? { '$search': `"${escapedQuery}"` } : {}),
|
|
63
|
+
},
|
|
64
|
+
}, scopedAgentId);
|
|
65
|
+
const messages = Array.isArray(result?.value) ? result.value : [];
|
|
66
|
+
return messages
|
|
67
|
+
.map((item) => ({
|
|
68
|
+
fingerprint: `outlook:${config.connectionId}:${item.id}`,
|
|
69
|
+
timestamp: item.receivedDateTime || new Date().toISOString(),
|
|
70
|
+
context: {
|
|
71
|
+
triggerEvent: {
|
|
72
|
+
provider: 'outlook',
|
|
73
|
+
messageId: item.id,
|
|
74
|
+
subject: item.subject || '',
|
|
75
|
+
from: item.from?.emailAddress?.address || null,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
}))
|
|
79
|
+
.sort(sortByTimestamp);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (triggerType === 'slack_message_received') {
|
|
83
|
+
const result = await integrationManager.executeTool(userId, 'slack_get_conversation_history', {
|
|
84
|
+
...connectionArg,
|
|
85
|
+
channel: config.channel,
|
|
86
|
+
limit: 20,
|
|
87
|
+
}, scopedAgentId);
|
|
88
|
+
const messages = Array.isArray(result?.result?.messages)
|
|
89
|
+
? result.result.messages
|
|
90
|
+
: Array.isArray(result?.messages)
|
|
91
|
+
? result.messages
|
|
92
|
+
: [];
|
|
93
|
+
return messages
|
|
94
|
+
.filter((item) => !config.sender || String(item.user || '') === String(config.sender))
|
|
95
|
+
.map((item) => ({
|
|
96
|
+
fingerprint: `slack:${config.connectionId}:${config.channel}:${item.ts}`,
|
|
97
|
+
timestamp: item.ts || new Date().toISOString(),
|
|
98
|
+
context: {
|
|
99
|
+
triggerEvent: {
|
|
100
|
+
provider: 'slack',
|
|
101
|
+
channel: config.channel,
|
|
102
|
+
sender: item.user || null,
|
|
103
|
+
messageId: item.client_msg_id || item.ts,
|
|
104
|
+
content: item.text || '',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}))
|
|
108
|
+
.sort(sortByTimestamp);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (triggerType === 'teams_message_received') {
|
|
112
|
+
const result = await integrationManager.executeTool(userId, 'microsoft_365_teams_graph_request', {
|
|
113
|
+
...connectionArg,
|
|
114
|
+
method: 'GET',
|
|
115
|
+
path: `/v1.0/me/chats/${encodeURIComponent(config.chatId)}/messages`,
|
|
116
|
+
query: { '$top': 20 },
|
|
117
|
+
}, scopedAgentId);
|
|
118
|
+
const messages = Array.isArray(result?.value) ? result.value : [];
|
|
119
|
+
return messages
|
|
120
|
+
.filter((item) => {
|
|
121
|
+
const sender = item.from?.user?.id || item.from?.application?.id || '';
|
|
122
|
+
return !config.sender || String(sender) === String(config.sender);
|
|
123
|
+
})
|
|
124
|
+
.map((item) => ({
|
|
125
|
+
fingerprint: `teams:${config.connectionId}:${config.chatId}:${item.id}`,
|
|
126
|
+
timestamp: item.createdDateTime || new Date().toISOString(),
|
|
127
|
+
context: {
|
|
128
|
+
triggerEvent: {
|
|
129
|
+
provider: 'teams',
|
|
130
|
+
chatId: config.chatId,
|
|
131
|
+
messageId: item.id,
|
|
132
|
+
sender: item.from?.user?.id || null,
|
|
133
|
+
content: item.body?.content || '',
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
}))
|
|
137
|
+
.sort(sortByTimestamp);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (triggerType === 'whatsapp_personal_message_received') {
|
|
141
|
+
const result = await integrationManager.executeTool(userId, 'whatsapp_personal_get_messages', {
|
|
142
|
+
...connectionArg,
|
|
143
|
+
chat_id: config.chatId,
|
|
144
|
+
limit: 25,
|
|
145
|
+
}, scopedAgentId);
|
|
146
|
+
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
147
|
+
return messages
|
|
148
|
+
.filter((item) => item && item.fromMe !== true)
|
|
149
|
+
.filter((item) => !config.sender || String(item.senderTag || item.sender || '') === String(config.sender))
|
|
150
|
+
.filter((item) => !(config.ignoreGroups && String(item.chatId || '').endsWith('@g.us')))
|
|
151
|
+
.map((item) => ({
|
|
152
|
+
fingerprint: `whatsapp:${config.connectionId}:${item.id}`,
|
|
153
|
+
timestamp: item.timestamp || new Date().toISOString(),
|
|
154
|
+
context: {
|
|
155
|
+
triggerEvent: {
|
|
156
|
+
provider: 'whatsapp_personal',
|
|
157
|
+
chatId: item.chatId,
|
|
158
|
+
messageId: item.id,
|
|
159
|
+
sender: item.sender || null,
|
|
160
|
+
senderTag: item.senderTag || null,
|
|
161
|
+
content: item.text || '',
|
|
162
|
+
isGroup: String(item.chatId || '').endsWith('@g.us'),
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
}))
|
|
166
|
+
.sort(sortByTimestamp);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function pollIntegrationTask(runtime, task) {
|
|
173
|
+
const config = normalizeJsonObject(task.trigger_config);
|
|
174
|
+
const rows = await fetchTriggerRows({
|
|
175
|
+
integrationManager: runtime.integrationManager,
|
|
176
|
+
userId: task.user_id,
|
|
177
|
+
agentId: task.agent_id,
|
|
178
|
+
triggerType: task.trigger_type,
|
|
179
|
+
config,
|
|
180
|
+
});
|
|
181
|
+
if (!rows.length) return;
|
|
182
|
+
|
|
183
|
+
const existingFingerprint = String(task.last_trigger_fingerprint || '');
|
|
184
|
+
const latestFingerprint = rows[rows.length - 1].fingerprint;
|
|
185
|
+
if (!existingFingerprint) {
|
|
186
|
+
runtime.taskRepository.markTaskTriggerCheckpoint(task.id, latestFingerprint);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const startIndex = rows.findIndex((row) => row.fingerprint === existingFingerprint);
|
|
191
|
+
const pending = startIndex >= 0 ? rows.slice(startIndex + 1) : rows.slice(-1);
|
|
192
|
+
for (const row of pending) {
|
|
193
|
+
await runtime.fireTaskFromTrigger(task.id, task.user_id, row);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function createWhatsappTriggerPayload(event) {
|
|
198
|
+
return {
|
|
199
|
+
fingerprint: `whatsapp:${event.connectionId}:${event.messageId}`,
|
|
200
|
+
timestamp: event.timestamp,
|
|
201
|
+
context: {
|
|
202
|
+
triggerEvent: {
|
|
203
|
+
provider: 'whatsapp_personal',
|
|
204
|
+
chatId: event.chatId,
|
|
205
|
+
sender: event.sender,
|
|
206
|
+
senderTag: event.senderTag || null,
|
|
207
|
+
messageId: event.messageId,
|
|
208
|
+
content: event.text || '',
|
|
209
|
+
isGroup: event.isGroup === true,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function matchesWhatsappTaskEvent(task, event) {
|
|
216
|
+
const config = normalizeJsonObject(task.trigger_config);
|
|
217
|
+
if (String(config.connectionId || '') !== String(event.connectionId || '')) return false;
|
|
218
|
+
if (config.chatId && String(config.chatId) !== String(event.chatId)) return false;
|
|
219
|
+
if (config.sender && String(config.sender) !== String(event.senderTag || event.sender || '')) return false;
|
|
220
|
+
if (config.ignoreGroups && event.isGroup) return false;
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function attachIntegrationEventSources(runtime) {
|
|
225
|
+
const cleanups = [];
|
|
226
|
+
const provider = runtime.integrationManager?.getProvider?.('whatsapp_personal');
|
|
227
|
+
if (provider && typeof provider.on === 'function') {
|
|
228
|
+
const listener = async (event) => {
|
|
229
|
+
const tasks = runtime.taskRepository.listEnabledWhatsappEventTasks(event.userId, event.agentId);
|
|
230
|
+
for (const task of tasks) {
|
|
231
|
+
if (!matchesWhatsappTaskEvent(task, event)) continue;
|
|
232
|
+
await runtime.fireTaskFromTrigger(task.id, task.user_id, createWhatsappTriggerPayload(event)).catch((error) => {
|
|
233
|
+
const logger = runtime.logger?.error || console.error;
|
|
234
|
+
logger('[Tasks] Failed to fire WhatsApp task trigger', {
|
|
235
|
+
taskId: task.id,
|
|
236
|
+
userId: task.user_id,
|
|
237
|
+
agentId: event.agentId,
|
|
238
|
+
error: error?.message || String(error),
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
provider.on('message', listener);
|
|
244
|
+
cleanups.push(() => {
|
|
245
|
+
if (typeof provider.off === 'function') {
|
|
246
|
+
provider.off('message', listener);
|
|
247
|
+
} else if (typeof provider.removeListener === 'function') {
|
|
248
|
+
provider.removeListener('message', listener);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return cleanups;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = {
|
|
256
|
+
POLLED_TRIGGER_TYPES,
|
|
257
|
+
attachIntegrationEventSources,
|
|
258
|
+
fetchTriggerRows,
|
|
259
|
+
pollIntegrationTask,
|
|
260
|
+
};
|