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.
Files changed (60) hide show
  1. package/.env.example +13 -0
  2. package/README.md +3 -1
  3. package/docs/automation.md +1 -1
  4. package/docs/capabilities.md +2 -2
  5. package/docs/configuration.md +14 -1
  6. package/docs/integrations.md +6 -1
  7. package/lib/manager.js +127 -1
  8. package/package.json +2 -1
  9. package/server/db/database.js +68 -0
  10. package/server/http/middleware.js +50 -0
  11. package/server/http/routes.js +3 -1
  12. package/server/index.js +1 -0
  13. package/server/public/.last_build_id +1 -1
  14. package/server/public/assets/NOTICES +61 -0
  15. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  16. package/server/public/flutter_bootstrap.js +1 -1
  17. package/server/public/main.dart.js +61049 -60444
  18. package/server/routes/integrations.js +97 -8
  19. package/server/routes/memory.js +11 -2
  20. package/server/routes/screenHistory.js +46 -0
  21. package/server/routes/triggers.js +81 -0
  22. package/server/services/ai/engine.js +9 -0
  23. package/server/services/ai/models.js +30 -0
  24. package/server/services/ai/providers/githubCopilot.js +97 -0
  25. package/server/services/ai/providers/openaiCodex.js +26 -0
  26. package/server/services/ai/settings.js +20 -0
  27. package/server/services/ai/systemPrompt.js +1 -1
  28. package/server/services/ai/tools.js +150 -11
  29. package/server/services/browser/controller.js +47 -3
  30. package/server/services/desktop/screenRecorder.js +126 -0
  31. package/server/services/integrations/env.js +19 -0
  32. package/server/services/integrations/github/common.js +106 -0
  33. package/server/services/integrations/github/provider.js +499 -0
  34. package/server/services/integrations/github/repos.js +1124 -0
  35. package/server/services/integrations/home_assistant/provider.js +630 -0
  36. package/server/services/integrations/manager.js +63 -7
  37. package/server/services/integrations/oauth_provider.js +13 -6
  38. package/server/services/integrations/provider_config_store.js +76 -0
  39. package/server/services/integrations/registry.js +10 -0
  40. package/server/services/integrations/spotify/provider.js +487 -0
  41. package/server/services/integrations/weather/provider.js +559 -0
  42. package/server/services/integrations/whatsapp/provider.js +6 -2
  43. package/server/services/manager.js +22 -0
  44. package/server/services/memory/manager.js +39 -2
  45. package/server/services/messaging/manager.js +29 -7
  46. package/server/services/skills/base_catalog.js +33 -0
  47. package/server/services/tasks/adapters/index.js +2 -0
  48. package/server/services/tasks/adapters/manual.js +12 -0
  49. package/server/services/tasks/adapters/schedule.js +33 -5
  50. package/server/services/tasks/adapters/weather_event.js +84 -0
  51. package/server/services/tasks/integration_runtime.js +85 -0
  52. package/server/services/tasks/runtime.js +2 -2
  53. package/server/services/voice/agentBridge.js +20 -4
  54. package/server/services/voice/message.js +3 -0
  55. package/server/services/voice/openaiClient.js +4 -1
  56. package/server/services/voice/providers.js +2 -1
  57. package/server/services/voice/runtimeManager.js +136 -1
  58. package/server/services/widgets/service.js +49 -4
  59. package/server/utils/local_secrets.js +56 -0
  60. 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(row, this._loadLatestSnapshotMap([widgetId]).get(widgetId) || null);
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
- return rows.map((row) => this._serializeWidget(row, snapshotMap.get(row.id) || null));
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
- _serializeWidget(row, latestSnapshot) {
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
+ };
@@ -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}`, summary);
96
+ console[level](redactSecrets(`${prefix} ${message}`), summary);
69
97
  }
70
98
 
71
99
  /**