neoagent 3.1.0 → 3.1.1-beta.0
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/flutter_app/lib/main_integrations.dart +252 -0
- package/flutter_app/lib/main_operations.dart +364 -322
- package/package.json +2 -1
- package/server/guest-agent.browser.package.json +1 -0
- package/server/public/.last_build_id +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 +38078 -37802
- package/server/services/integrations/neomail/provider.js +993 -0
- package/server/services/integrations/registry.js +2 -0
- package/server/services/tasks/adapters/index.js +1 -1
- package/server/services/tasks/adapters/neomail_email_received.js +46 -0
- package/server/services/tasks/integration_runtime.js +67 -3
- package/server/services/tasks/runtime.js +6 -1
- package/server/services/tasks/task_repository.js +13 -6
|
@@ -11,6 +11,7 @@ const { createSpotifyProvider } = require('./spotify/provider');
|
|
|
11
11
|
const { createSlackProvider } = require('./slack/provider');
|
|
12
12
|
const { createWeatherProvider } = require('./weather/provider');
|
|
13
13
|
const { createWhatsAppPersonalProvider } = require('./whatsapp');
|
|
14
|
+
const { createNeoMailProvider } = require('./neomail/provider');
|
|
14
15
|
|
|
15
16
|
function createIntegrationRegistry(options = {}) {
|
|
16
17
|
const providers = [
|
|
@@ -23,6 +24,7 @@ function createIntegrationRegistry(options = {}) {
|
|
|
23
24
|
createTrelloProvider(),
|
|
24
25
|
createWeatherProvider(),
|
|
25
26
|
createSpotifyProvider(),
|
|
27
|
+
createNeoMailProvider(),
|
|
26
28
|
createHomeAssistantProvider(),
|
|
27
29
|
createWhatsAppPersonalProvider(options),
|
|
28
30
|
];
|
|
@@ -5,11 +5,11 @@ module.exports = [
|
|
|
5
5
|
require('./manual'),
|
|
6
6
|
require('./webhook'),
|
|
7
7
|
require('./gmail_message_received'),
|
|
8
|
+
require('./neomail_email_received'),
|
|
8
9
|
require('./outlook_email_received'),
|
|
9
10
|
require('./slack_message_received'),
|
|
10
11
|
require('./teams_message_received'),
|
|
11
12
|
require('./weather_event'),
|
|
12
|
-
require('./webhook'),
|
|
13
13
|
require('./whatsapp_personal_message_received'),
|
|
14
14
|
require('./android_notification_received'),
|
|
15
15
|
];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
ensureOwnedIntegrationConnection,
|
|
5
|
+
normalizeBoolean,
|
|
6
|
+
normalizeTrimmedText,
|
|
7
|
+
} = require('../security');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
type: 'neomail_email_received',
|
|
11
|
+
label: 'NeoMail Email Received',
|
|
12
|
+
providerKey: 'neomail',
|
|
13
|
+
appKey: 'mailbox',
|
|
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: 'neomail',
|
|
20
|
+
appKey: 'mailbox',
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
connectionId: connection.id,
|
|
24
|
+
accountEmail: connection.account_email || null,
|
|
25
|
+
mailAccountId: normalizeTrimmedText(
|
|
26
|
+
config.mailAccountId || config.mail_account_id,
|
|
27
|
+
120,
|
|
28
|
+
),
|
|
29
|
+
folder: normalizeTrimmedText(config.folder, 200),
|
|
30
|
+
query: normalizeTrimmedText(config.query, 500),
|
|
31
|
+
unreadOnly: normalizeBoolean(
|
|
32
|
+
config.unreadOnly ?? config.unread_only,
|
|
33
|
+
false,
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
summarize(config = {}) {
|
|
38
|
+
const parts = ['NeoMail'];
|
|
39
|
+
if (config.accountEmail) parts.push(config.accountEmail);
|
|
40
|
+
if (config.mailAccountId) parts.push(`mailbox: ${config.mailAccountId}`);
|
|
41
|
+
if (config.folder) parts.push(`folder: ${config.folder}`);
|
|
42
|
+
if (config.query) parts.push(`query: ${config.query}`);
|
|
43
|
+
if (config.unreadOnly) parts.push('unread only');
|
|
44
|
+
return parts.join(' · ');
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -5,6 +5,7 @@ const { normalizeJsonObject } = require('./utils');
|
|
|
5
5
|
|
|
6
6
|
const POLLED_TRIGGER_TYPES = Object.freeze([
|
|
7
7
|
'gmail_message_received',
|
|
8
|
+
'neomail_email_received',
|
|
8
9
|
'outlook_email_received',
|
|
9
10
|
'slack_message_received',
|
|
10
11
|
'teams_message_received',
|
|
@@ -13,10 +14,19 @@ const POLLED_TRIGGER_TYPES = Object.freeze([
|
|
|
13
14
|
]);
|
|
14
15
|
|
|
15
16
|
function sortByTimestamp(left, right) {
|
|
16
|
-
|
|
17
|
+
const timeOrder = String(left.timestamp).localeCompare(String(right.timestamp));
|
|
18
|
+
if (timeOrder !== 0) return timeOrder;
|
|
19
|
+
return String(left.fingerprint).localeCompare(String(right.fingerprint));
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
async function fetchTriggerRows({
|
|
22
|
+
async function fetchTriggerRows({
|
|
23
|
+
integrationManager,
|
|
24
|
+
userId,
|
|
25
|
+
agentId,
|
|
26
|
+
triggerType,
|
|
27
|
+
config,
|
|
28
|
+
lastTriggeredAt,
|
|
29
|
+
}) {
|
|
20
30
|
if (!integrationManager) return [];
|
|
21
31
|
const scopedAgentId = resolveAgentId(userId, agentId);
|
|
22
32
|
const connectionArg = {
|
|
@@ -80,6 +90,53 @@ async function fetchTriggerRows({ integrationManager, userId, agentId, triggerTy
|
|
|
80
90
|
.sort(sortByTimestamp);
|
|
81
91
|
}
|
|
82
92
|
|
|
93
|
+
if (triggerType === 'neomail_email_received') {
|
|
94
|
+
const provider = integrationManager.getProvider('neomail');
|
|
95
|
+
if (!provider || typeof provider.fetchTriggerEvents !== 'function') {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const connectionId = Number(config.connectionId);
|
|
99
|
+
if (!Number.isInteger(connectionId) || connectionId <= 0) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const connection = integrationManager.getConnectionById(
|
|
103
|
+
userId,
|
|
104
|
+
connectionId,
|
|
105
|
+
scopedAgentId,
|
|
106
|
+
);
|
|
107
|
+
if (
|
|
108
|
+
!connection ||
|
|
109
|
+
String(connection.provider_key || '').trim() !== 'neomail' ||
|
|
110
|
+
String(connection.app_key || '').trim() !== 'mailbox' ||
|
|
111
|
+
String(connection.status || '').trim() !== 'connected'
|
|
112
|
+
) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const result = await provider.fetchTriggerEvents({
|
|
116
|
+
connection,
|
|
117
|
+
config,
|
|
118
|
+
since: lastTriggeredAt,
|
|
119
|
+
limit: 100,
|
|
120
|
+
});
|
|
121
|
+
if (result?.credentials) {
|
|
122
|
+
const existingCredentials = integrationManager.parseCredentials(
|
|
123
|
+
connection.credentials_json,
|
|
124
|
+
);
|
|
125
|
+
const mergedCredentials = integrationManager.mergeUpdatedCredentials(
|
|
126
|
+
existingCredentials,
|
|
127
|
+
result.credentials,
|
|
128
|
+
);
|
|
129
|
+
integrationManager.persistSharedCredentials(
|
|
130
|
+
userId,
|
|
131
|
+
scopedAgentId,
|
|
132
|
+
'neomail',
|
|
133
|
+
connection.account_email,
|
|
134
|
+
mergedCredentials,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return Array.isArray(result?.rows) ? result.rows.slice().sort(sortByTimestamp) : [];
|
|
138
|
+
}
|
|
139
|
+
|
|
83
140
|
if (triggerType === 'slack_message_received') {
|
|
84
141
|
const result = await integrationManager.executeTool(userId, 'slack_get_conversation_history', {
|
|
85
142
|
...connectionArg,
|
|
@@ -262,13 +319,20 @@ async function pollIntegrationTask(runtime, task) {
|
|
|
262
319
|
agentId: task.agent_id,
|
|
263
320
|
triggerType: task.trigger_type,
|
|
264
321
|
config,
|
|
322
|
+
lastTriggeredAt: task.last_triggered_at,
|
|
265
323
|
});
|
|
266
324
|
if (!rows.length) return;
|
|
267
325
|
|
|
268
326
|
const existingFingerprint = String(task.last_trigger_fingerprint || '');
|
|
269
327
|
const latestFingerprint = rows[rows.length - 1].fingerprint;
|
|
328
|
+
const latestTimestamp = rows[rows.length - 1].timestamp;
|
|
270
329
|
if (!existingFingerprint) {
|
|
271
|
-
runtime.taskRepository.markTaskTriggerCheckpoint(
|
|
330
|
+
runtime.taskRepository.markTaskTriggerCheckpoint(
|
|
331
|
+
task.id,
|
|
332
|
+
latestFingerprint,
|
|
333
|
+
task.user_id,
|
|
334
|
+
latestTimestamp,
|
|
335
|
+
);
|
|
272
336
|
return;
|
|
273
337
|
}
|
|
274
338
|
|
|
@@ -332,7 +332,12 @@ class TaskRuntime {
|
|
|
332
332
|
triggerPayload: triggerPayload.context || {},
|
|
333
333
|
});
|
|
334
334
|
if (!result?.error && !result?.skipped) {
|
|
335
|
-
this.taskRepository.markTaskTriggered(
|
|
335
|
+
this.taskRepository.markTaskTriggered(
|
|
336
|
+
taskId,
|
|
337
|
+
userId,
|
|
338
|
+
fingerprint,
|
|
339
|
+
triggerPayload.timestamp,
|
|
340
|
+
);
|
|
336
341
|
}
|
|
337
342
|
return result;
|
|
338
343
|
}
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
const db = require('../../db/database');
|
|
4
4
|
|
|
5
5
|
class TaskRepository {
|
|
6
|
+
normalizeTriggerTimestamp(value) {
|
|
7
|
+
const text = String(value || '').trim();
|
|
8
|
+
if (!text) return new Date().toISOString();
|
|
9
|
+
const parsed = new Date(text);
|
|
10
|
+
return Number.isNaN(parsed.getTime()) ? new Date().toISOString() : parsed.toISOString();
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
createTask(userId, normalizedTask) {
|
|
7
14
|
const result = db.prepare(
|
|
8
15
|
`INSERT INTO scheduled_tasks (
|
|
@@ -124,20 +131,20 @@ class TaskRepository {
|
|
|
124
131
|
).all(userId, agentId);
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
markTaskTriggered(taskId, userId, fingerprint) {
|
|
134
|
+
markTaskTriggered(taskId, userId, fingerprint, triggeredAt = null) {
|
|
128
135
|
db.prepare(
|
|
129
136
|
`UPDATE scheduled_tasks
|
|
130
|
-
SET last_triggered_at =
|
|
137
|
+
SET last_triggered_at = ?, last_trigger_fingerprint = ?
|
|
131
138
|
WHERE id = ? AND user_id = ?`
|
|
132
|
-
).run(fingerprint, taskId, userId);
|
|
139
|
+
).run(this.normalizeTriggerTimestamp(triggeredAt), fingerprint, taskId, userId);
|
|
133
140
|
}
|
|
134
141
|
|
|
135
|
-
markTaskTriggerCheckpoint(taskId, fingerprint, userId) {
|
|
142
|
+
markTaskTriggerCheckpoint(taskId, fingerprint, userId, triggeredAt = null) {
|
|
136
143
|
db.prepare(
|
|
137
144
|
`UPDATE scheduled_tasks
|
|
138
|
-
SET last_triggered_at =
|
|
145
|
+
SET last_triggered_at = ?, last_trigger_fingerprint = ?
|
|
139
146
|
WHERE id = ? AND user_id = ?`
|
|
140
|
-
).run(fingerprint, taskId, userId);
|
|
147
|
+
).run(this.normalizeTriggerTimestamp(triggeredAt), fingerprint, taskId, userId);
|
|
141
148
|
}
|
|
142
149
|
|
|
143
150
|
markTaskRun(taskId, userId) {
|