neoagent 2.3.0 → 2.3.1-beta.10
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 +13 -0
- package/README.md +3 -1
- package/docs/automation.md +1 -1
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +14 -1
- package/docs/integrations.md +6 -1
- package/lib/manager.js +127 -1
- package/package.json +2 -1
- package/server/db/database.js +68 -0
- package/server/http/middleware.js +50 -0
- package/server/http/routes.js +3 -1
- package/server/index.js +1 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +61 -0
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +61049 -60444
- package/server/routes/integrations.js +97 -8
- package/server/routes/memory.js +11 -2
- package/server/routes/screenHistory.js +46 -0
- package/server/routes/triggers.js +81 -0
- package/server/services/ai/engine.js +9 -0
- package/server/services/ai/models.js +30 -0
- package/server/services/ai/providers/githubCopilot.js +97 -0
- package/server/services/ai/providers/openaiCodex.js +26 -0
- package/server/services/ai/settings.js +20 -0
- package/server/services/ai/systemPrompt.js +1 -1
- package/server/services/ai/tools.js +150 -11
- package/server/services/browser/controller.js +47 -3
- package/server/services/desktop/screenRecorder.js +126 -0
- package/server/services/integrations/env.js +19 -0
- package/server/services/integrations/github/common.js +106 -0
- package/server/services/integrations/github/provider.js +499 -0
- package/server/services/integrations/github/repos.js +1124 -0
- package/server/services/integrations/home_assistant/provider.js +630 -0
- package/server/services/integrations/manager.js +63 -7
- package/server/services/integrations/oauth_provider.js +13 -6
- package/server/services/integrations/provider_config_store.js +76 -0
- package/server/services/integrations/registry.js +10 -0
- package/server/services/integrations/spotify/provider.js +487 -0
- package/server/services/integrations/weather/provider.js +559 -0
- package/server/services/integrations/whatsapp/provider.js +6 -2
- package/server/services/manager.js +22 -0
- package/server/services/memory/manager.js +39 -2
- package/server/services/messaging/manager.js +29 -7
- package/server/services/skills/base_catalog.js +33 -0
- package/server/services/tasks/adapters/index.js +2 -0
- package/server/services/tasks/adapters/manual.js +12 -0
- package/server/services/tasks/adapters/schedule.js +33 -5
- package/server/services/tasks/adapters/weather_event.js +84 -0
- package/server/services/tasks/integration_runtime.js +85 -0
- package/server/services/tasks/runtime.js +2 -2
- package/server/services/voice/agentBridge.js +20 -4
- package/server/services/voice/message.js +3 -0
- package/server/services/voice/openaiClient.js +4 -1
- package/server/services/voice/providers.js +2 -1
- package/server/services/voice/runtimeManager.js +136 -1
- package/server/services/widgets/service.js +49 -4
- package/server/utils/local_secrets.js +56 -0
- package/server/utils/logger.js +37 -9
|
@@ -243,7 +243,11 @@ class WidgetService {
|
|
|
243
243
|
WHERE id = ? AND user_id = ?`
|
|
244
244
|
).get(widgetId, userId);
|
|
245
245
|
if (!row) return null;
|
|
246
|
-
return this._serializeWidget(
|
|
246
|
+
return this._serializeWidget(
|
|
247
|
+
row,
|
|
248
|
+
this._loadLatestSnapshotMap([widgetId]).get(widgetId) || null,
|
|
249
|
+
this._loadWidgetTasksMap([widgetId], userId).get(widgetId) || []
|
|
250
|
+
);
|
|
247
251
|
}
|
|
248
252
|
|
|
249
253
|
listWidgets(userId, { agentId = null } = {}) {
|
|
@@ -262,7 +266,8 @@ class WidgetService {
|
|
|
262
266
|
ORDER BY updated_at DESC, created_at DESC`
|
|
263
267
|
).all(userId);
|
|
264
268
|
const snapshotMap = this._loadLatestSnapshotMap(rows.map((row) => row.id));
|
|
265
|
-
|
|
269
|
+
const tasksMap = this._loadWidgetTasksMap(rows.map((row) => row.id), userId);
|
|
270
|
+
return rows.map((row) => this._serializeWidget(row, snapshotMap.get(row.id) || null, tasksMap.get(row.id) || []));
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
listLatestSnapshots(userId, { agentId = null } = {}) {
|
|
@@ -343,7 +348,7 @@ class WidgetService {
|
|
|
343
348
|
throw new Error('Widget not found.');
|
|
344
349
|
}
|
|
345
350
|
|
|
346
|
-
const current = this._serializeWidget(existingRow, null);
|
|
351
|
+
const current = this._serializeWidget(existingRow, null, []);
|
|
347
352
|
const normalized = normalizeWidgetInput({
|
|
348
353
|
name: input.name ?? current.name,
|
|
349
354
|
template: input.template ?? current.template,
|
|
@@ -581,7 +586,46 @@ class WidgetService {
|
|
|
581
586
|
return map;
|
|
582
587
|
}
|
|
583
588
|
|
|
584
|
-
|
|
589
|
+
_loadWidgetTasksMap(widgetIds, userId) {
|
|
590
|
+
const ids = Array.from(new Set(widgetIds.filter(Boolean)));
|
|
591
|
+
const map = new Map();
|
|
592
|
+
if (!ids.length) return map;
|
|
593
|
+
|
|
594
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
595
|
+
const params = [userId, ...ids];
|
|
596
|
+
|
|
597
|
+
// We filter tasks where task_type is NOT 'widget_refresh'
|
|
598
|
+
// and where the task_config contains the widgetId.
|
|
599
|
+
const rows = db.prepare(
|
|
600
|
+
`SELECT id, name, trigger_type, enabled, task_config
|
|
601
|
+
FROM scheduled_tasks
|
|
602
|
+
WHERE user_id = ?
|
|
603
|
+
AND task_type != 'widget_refresh'
|
|
604
|
+
AND json_extract(task_config, '$.widgetId') IN (${placeholders})
|
|
605
|
+
ORDER BY created_at ASC`
|
|
606
|
+
).all(...params);
|
|
607
|
+
|
|
608
|
+
for (const row of rows) {
|
|
609
|
+
const config = parseJsonObject(row.task_config, {});
|
|
610
|
+
const widgetId = config.widgetId;
|
|
611
|
+
if (!widgetId) continue;
|
|
612
|
+
|
|
613
|
+
const task = {
|
|
614
|
+
id: row.id,
|
|
615
|
+
name: row.name,
|
|
616
|
+
triggerType: row.trigger_type,
|
|
617
|
+
enabled: row.enabled !== 0 && row.enabled !== false,
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
if (!map.has(widgetId)) {
|
|
621
|
+
map.set(widgetId, []);
|
|
622
|
+
}
|
|
623
|
+
map.get(widgetId).push(task);
|
|
624
|
+
}
|
|
625
|
+
return map;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
_serializeWidget(row, latestSnapshot, tasks = []) {
|
|
585
629
|
const definition = parseJsonObject(row.definition_json, {});
|
|
586
630
|
return {
|
|
587
631
|
id: row.id,
|
|
@@ -600,6 +644,7 @@ class WidgetService {
|
|
|
600
644
|
updatedAt: row.updated_at || null,
|
|
601
645
|
nextRefresh: row.refresh_cron ? findNextRun(row.refresh_cron)?.toISOString() || null : null,
|
|
602
646
|
latestSnapshot,
|
|
647
|
+
tasks,
|
|
603
648
|
};
|
|
604
649
|
}
|
|
605
650
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { getSessionSecret } = require('../services/account/session_secret');
|
|
5
|
+
|
|
6
|
+
const LOCAL_SECRET_PREFIX = 'enc:local:v1:';
|
|
7
|
+
|
|
8
|
+
function getLocalSecretMaterial() {
|
|
9
|
+
const configured = String(process.env.NEOAGENT_DATA_ENCRYPTION_KEY || '').trim();
|
|
10
|
+
if (configured) return configured;
|
|
11
|
+
return getSessionSecret();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function deriveKey() {
|
|
15
|
+
return crypto.createHash('sha256').update(getLocalSecretMaterial()).digest();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function isLocalEncryptedValue(value) {
|
|
19
|
+
return String(value || '').startsWith(LOCAL_SECRET_PREFIX);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function encryptLocalValue(value) {
|
|
23
|
+
const text = String(value || '');
|
|
24
|
+
if (!text) return '';
|
|
25
|
+
if (isLocalEncryptedValue(text)) return text;
|
|
26
|
+
|
|
27
|
+
const iv = crypto.randomBytes(12);
|
|
28
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', deriveKey(), iv);
|
|
29
|
+
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
|
|
30
|
+
const tag = cipher.getAuthTag();
|
|
31
|
+
return `${LOCAL_SECRET_PREFIX}${Buffer.concat([iv, tag, encrypted]).toString('base64')}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function decryptLocalValue(value) {
|
|
35
|
+
const text = String(value || '');
|
|
36
|
+
if (!text) return '';
|
|
37
|
+
if (!isLocalEncryptedValue(text)) return text;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const payload = Buffer.from(text.slice(LOCAL_SECRET_PREFIX.length), 'base64');
|
|
41
|
+
const iv = payload.subarray(0, 12);
|
|
42
|
+
const tag = payload.subarray(12, 28);
|
|
43
|
+
const ciphertext = payload.subarray(28);
|
|
44
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', deriveKey(), iv);
|
|
45
|
+
decipher.setAuthTag(tag);
|
|
46
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
|
|
47
|
+
} catch {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
decryptLocalValue,
|
|
54
|
+
encryptLocalValue,
|
|
55
|
+
isLocalEncryptedValue,
|
|
56
|
+
};
|
package/server/utils/logger.js
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const SENSITIVE_KEY_RE = /(?:^|_|-)(?:token|secret|password|api[_-]?key|authorization|cookie|session(?:id)?|sid)(?:$|_|-)/i;
|
|
4
|
+
|
|
5
|
+
function redactSecrets(input) {
|
|
6
|
+
let text = String(input || '');
|
|
7
|
+
if (!text) return text;
|
|
8
|
+
|
|
9
|
+
text = text
|
|
10
|
+
.replace(/\b(Bearer\s+)[A-Za-z0-9._~+\/-]+=*/gi, '$1[redacted]')
|
|
11
|
+
.replace(/([?&](?:token|access_token|refresh_token|api[_-]?key|secret|password|authorization|cookie|session|sid)=)([^&#\s]+)/gi, '$1[redacted]')
|
|
12
|
+
.replace(/\b(token|access_token|refresh_token|authorization|api[_-]?key|secret|password|cookie|session(?:id)?|sid)\b\s*[:=]\s*("[^"]*"|'[^']*'|[^,\s;]+)/gi, '$1=[redacted]');
|
|
13
|
+
|
|
14
|
+
return text;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function sanitizeUrl(value) {
|
|
18
|
+
return redactSecrets(String(value || ''));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isSensitiveKey(key) {
|
|
22
|
+
return SENSITIVE_KEY_RE.test(String(key || ''));
|
|
23
|
+
}
|
|
24
|
+
|
|
3
25
|
function serializeValue(value, seen = new WeakSet()) {
|
|
4
26
|
if (value instanceof Error) {
|
|
5
27
|
return JSON.stringify({
|
|
6
28
|
name: value.name,
|
|
7
|
-
message: value.message,
|
|
8
|
-
stack: value.stack,
|
|
29
|
+
message: redactSecrets(value.message),
|
|
30
|
+
stack: redactSecrets(value.stack),
|
|
9
31
|
code: value.code,
|
|
10
32
|
cause: value.cause instanceof Error
|
|
11
|
-
? { name: value.cause.name, message: value.cause.message, stack: value.cause.stack }
|
|
33
|
+
? { name: value.cause.name, message: redactSecrets(value.cause.message), stack: redactSecrets(value.cause.stack) }
|
|
12
34
|
: value.cause
|
|
13
35
|
});
|
|
14
36
|
}
|
|
@@ -22,7 +44,7 @@ function serializeValue(value, seen = new WeakSet()) {
|
|
|
22
44
|
}
|
|
23
45
|
|
|
24
46
|
if (!value || typeof value !== 'object') {
|
|
25
|
-
return String(value);
|
|
47
|
+
return redactSecrets(String(value));
|
|
26
48
|
}
|
|
27
49
|
|
|
28
50
|
if (seen.has(value)) {
|
|
@@ -33,21 +55,27 @@ function serializeValue(value, seen = new WeakSet()) {
|
|
|
33
55
|
|
|
34
56
|
try {
|
|
35
57
|
return JSON.stringify(value, (key, nestedValue) => {
|
|
58
|
+
if (isSensitiveKey(key)) {
|
|
59
|
+
return '[redacted]';
|
|
60
|
+
}
|
|
36
61
|
if (nestedValue instanceof Error) {
|
|
37
62
|
return {
|
|
38
63
|
name: nestedValue.name,
|
|
39
|
-
message: nestedValue.message,
|
|
40
|
-
stack: nestedValue.stack,
|
|
64
|
+
message: redactSecrets(nestedValue.message),
|
|
65
|
+
stack: redactSecrets(nestedValue.stack),
|
|
41
66
|
code: nestedValue.code
|
|
42
67
|
};
|
|
43
68
|
}
|
|
44
69
|
if (typeof nestedValue === 'bigint') {
|
|
45
70
|
return nestedValue.toString();
|
|
46
71
|
}
|
|
72
|
+
if (typeof nestedValue === 'string') {
|
|
73
|
+
return redactSecrets(nestedValue);
|
|
74
|
+
}
|
|
47
75
|
return nestedValue;
|
|
48
76
|
});
|
|
49
77
|
} catch (err) {
|
|
50
|
-
return `[Unserializable object: ${err.message}]`;
|
|
78
|
+
return `[Unserializable object: ${redactSecrets(err.message)}]`;
|
|
51
79
|
} finally {
|
|
52
80
|
seen.delete(value);
|
|
53
81
|
}
|
|
@@ -58,14 +86,14 @@ function formatLogArgs(args) {
|
|
|
58
86
|
}
|
|
59
87
|
|
|
60
88
|
function logRequestSummary(level, req, message, extra = null) {
|
|
61
|
-
const prefix = `[HTTP] ${req.method} ${req.originalUrl || req.url}`;
|
|
89
|
+
const prefix = `[HTTP] ${req.method} ${sanitizeUrl(req.originalUrl || req.url)}`;
|
|
62
90
|
const summary = {
|
|
63
91
|
ip: req.ip,
|
|
64
92
|
userId: req.session?.userId || null,
|
|
65
93
|
userAgent: req.get?.('user-agent') || null,
|
|
66
94
|
...extra
|
|
67
95
|
};
|
|
68
|
-
console[level](`${prefix} ${message}
|
|
96
|
+
console[level](redactSecrets(`${prefix} ${message}`), summary);
|
|
69
97
|
}
|
|
70
98
|
|
|
71
99
|
/**
|