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
@@ -13,6 +13,22 @@ const {
13
13
 
14
14
  const OAUTH_STATE_PATTERN = /^[a-f0-9]{32,128}$/i;
15
15
 
16
+ function isLikelyExpiredConnectionError(error) {
17
+ const message = String(error?.message || error || '').toLowerCase();
18
+ if (!message) return false;
19
+ return [
20
+ 'invalid_grant',
21
+ 'token refresh failed',
22
+ 'token expired',
23
+ 'access token is missing',
24
+ 'refresh token is missing',
25
+ 'reconnect this integration account',
26
+ 'account is no longer authorized',
27
+ 'reauthorize',
28
+ 're-authorize',
29
+ ].some((hint) => message.includes(hint));
30
+ }
31
+
16
32
  class IntegrationManager {
17
33
  constructor(options = {}) {
18
34
  this.app = options.app || null;
@@ -147,7 +163,12 @@ class IntegrationManager {
147
163
 
148
164
  return this.registry
149
165
  .list()
150
- .map((provider) => provider.buildSnapshot(rowsByProvider.get(provider.key) || []));
166
+ .map((provider) =>
167
+ provider.buildSnapshot(rowsByProvider.get(provider.key) || [], {
168
+ userId,
169
+ agentId: scopedAgentId,
170
+ }),
171
+ );
151
172
  }
152
173
 
153
174
  async beginOAuth(userId, providerKey, options = {}) {
@@ -163,7 +184,10 @@ class IntegrationManager {
163
184
  throw new Error(`Unknown ${provider.label} app: ${appKey || 'missing app key'}`);
164
185
  }
165
186
 
166
- const env = provider.getEnvStatus();
187
+ const env = provider.getEnvStatus({
188
+ userId,
189
+ agentId,
190
+ });
167
191
  if (!env.configured) {
168
192
  throw new Error(env.summary);
169
193
  }
@@ -389,7 +413,10 @@ class IntegrationManager {
389
413
  getToolDefinitions(userId, agentId = null) {
390
414
  const definitions = [];
391
415
  for (const provider of this.registry.list()) {
392
- const env = provider.getEnvStatus();
416
+ const env = provider.getEnvStatus({
417
+ userId,
418
+ agentId,
419
+ });
393
420
  if (!env.configured) continue;
394
421
  const connections = this.listConnections(userId, provider.key, agentId);
395
422
  const connectedAppIds = Array.from(
@@ -411,9 +438,15 @@ class IntegrationManager {
411
438
  if (!provider) {
412
439
  throw new Error(`Unknown integration provider: ${providerKey}`);
413
440
  }
414
- const env = provider.getEnvStatus();
441
+ const env = provider.getEnvStatus({
442
+ userId,
443
+ agentId,
444
+ });
415
445
  const connections = this.listConnections(userId, provider.key, agentId);
416
- const snapshot = provider.buildSnapshot(connections);
446
+ const snapshot = provider.buildSnapshot(connections, {
447
+ userId,
448
+ agentId,
449
+ });
417
450
  const connectedAppIds = snapshot.apps
418
451
  .filter((app) => app.connection.connected)
419
452
  .map((app) => app.id);
@@ -582,7 +615,10 @@ class IntegrationManager {
582
615
  for (const provider of this.registry.list()) {
583
616
  if (!provider.supportsTool(toolName)) continue;
584
617
  foundSupportingProvider = true;
585
- const env = provider.getEnvStatus();
618
+ const env = provider.getEnvStatus({
619
+ userId,
620
+ agentId,
621
+ });
586
622
  if (!env.configured) {
587
623
  return { error: env.summary };
588
624
  }
@@ -607,6 +643,17 @@ class IntegrationManager {
607
643
  selection.connection,
608
644
  );
609
645
  } catch (err) {
646
+ if (isLikelyExpiredConnectionError(err)) {
647
+ db.prepare(
648
+ `UPDATE integration_connections
649
+ SET status = 'expired', updated_at = datetime('now')
650
+ WHERE id = ? AND user_id = ? AND agent_id = ?`,
651
+ ).run(
652
+ selection.connection.id,
653
+ userId,
654
+ resolveAgentId(userId, agentId),
655
+ );
656
+ }
610
657
  return { error: err?.message || 'execution_error' };
611
658
  }
612
659
  if (!execution) {
@@ -641,7 +688,13 @@ class IntegrationManager {
641
688
  summarizeConnectedProviders(userId, agentId = null) {
642
689
  const providers = this.registry.list().map((provider) => ({
643
690
  provider,
644
- snapshot: provider.buildSnapshot(this.listConnections(userId, provider.key, agentId)),
691
+ snapshot: provider.buildSnapshot(
692
+ this.listConnections(userId, provider.key, agentId),
693
+ {
694
+ userId,
695
+ agentId,
696
+ },
697
+ ),
645
698
  }));
646
699
 
647
700
  if (providers.length === 0) {
@@ -655,6 +708,9 @@ class IntegrationManager {
655
708
  }
656
709
 
657
710
  if (!snapshot?.env?.configured) {
711
+ if (snapshot?.env?.setupMode === 'user') {
712
+ return `${provider.label}: setup is not complete for this user yet. If the user wants to use it, tell them to finish setup in Official Integrations first.`;
713
+ }
658
714
  return `${provider.label}: available but not configured on the server yet. If the user wants to use it, tell them to finish setup in Official Integrations first.`;
659
715
  }
660
716
 
@@ -247,8 +247,8 @@ function createOAuthProvider(options = {}) {
247
247
  getToolAppId(toolName) {
248
248
  return toolAppMap.get(String(toolName || '').trim()) || null;
249
249
  },
250
- getEnvStatus() {
251
- return options.getEnvStatus();
250
+ getEnvStatus(context = {}) {
251
+ return options.getEnvStatus(context);
252
252
  },
253
253
  getToolDefinitions(toolOptions = {}) {
254
254
  const connectedAppIds = new Set(toolOptions.connectedAppIds || []);
@@ -257,8 +257,8 @@ function createOAuthProvider(options = {}) {
257
257
  supportsTool(toolName) {
258
258
  return toolAppMap.has(String(toolName || '').trim());
259
259
  },
260
- buildSnapshot(connectionRows) {
261
- const env = this.getEnvStatus();
260
+ buildSnapshot(connectionRows, context = {}) {
261
+ const env = this.getEnvStatus(context);
262
262
  const byApp = new Map();
263
263
  for (const row of Array.isArray(connectionRows) ? connectionRows : []) {
264
264
  const appId = String(row.app_key || '').trim();
@@ -319,7 +319,7 @@ function createOAuthProvider(options = {}) {
319
319
  throw new Error(`Unknown ${this.label} app: ${appKey}`);
320
320
  }
321
321
  const normalizedState = assertValidOAuthState(state);
322
- const env = this.getEnvStatus();
322
+ const env = this.getEnvStatus({ userId });
323
323
  if (!env.configured) {
324
324
  throw new Error(env.summary);
325
325
  }
@@ -343,7 +343,7 @@ function createOAuthProvider(options = {}) {
343
343
  userId,
344
344
  state: normalizedState,
345
345
  app,
346
- env: this.getEnvStatus(),
346
+ env: this.getEnvStatus({ userId }),
347
347
  });
348
348
  },
349
349
  async disconnect(connectionRow) {
@@ -368,12 +368,16 @@ function createOAuthProvider(options = {}) {
368
368
  appId: toolAppMap.get(String(toolName || '').trim()) || connectionRow.app_key,
369
369
  connection: connectionRow,
370
370
  credentials,
371
+ env: this.getEnvStatus({ userId: connectionRow?.user_id }),
371
372
  });
372
373
  },
373
374
  summarizeConnection(connectionRows) {
374
375
  const snapshot = this.buildSnapshot(connectionRows);
375
376
  if (!snapshot.connection.connected) {
376
377
  if (snapshot.connection.status === 'env_not_configured') {
378
+ if (snapshot?.env?.setupMode === 'user') {
379
+ return `${this.label} still needs per-user setup before accounts can connect.`;
380
+ }
377
381
  return `${this.label} still needs administrator setup before accounts can connect.`;
378
382
  }
379
383
  return `${this.label} is not connected.`;
@@ -392,6 +396,9 @@ function createOAuthProvider(options = {}) {
392
396
  },
393
397
  summarizeForModel(snapshot) {
394
398
  if (!snapshot?.env?.configured) {
399
+ if (snapshot?.env?.setupMode === 'user') {
400
+ return `${this.label}: setup is user-managed and is not complete yet. If the user wants to use it, tell them to open Official Integrations and finish setup in their account first.`;
401
+ }
395
402
  return `${this.label}: workspace setup is not complete yet. If the user wants to use it, tell them to open Official Integrations and ask an administrator to finish setup first.`;
396
403
  }
397
404
 
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const db = require('../../db/database');
4
+ const { decryptValue, encryptValue } = require('./secrets');
5
+
6
+ function normalizeProviderKey(providerKey) {
7
+ return String(providerKey || '').trim();
8
+ }
9
+
10
+ function parseConfig(value) {
11
+ try {
12
+ const parsed = JSON.parse(decryptValue(value || '{}') || '{}');
13
+ return parsed && typeof parsed === 'object' ? parsed : {};
14
+ } catch {
15
+ return {};
16
+ }
17
+ }
18
+
19
+ function getProviderConfig(userId, providerKey) {
20
+ const normalizedProviderKey = normalizeProviderKey(providerKey);
21
+ if (!Number.isInteger(Number(userId)) || Number(userId) <= 0 || !normalizedProviderKey) {
22
+ return {};
23
+ }
24
+
25
+ const row = db
26
+ .prepare(
27
+ `SELECT config_json
28
+ FROM integration_provider_configs
29
+ WHERE user_id = ? AND provider_key = ?`,
30
+ )
31
+ .get(Number(userId), normalizedProviderKey);
32
+
33
+ return parseConfig(row?.config_json);
34
+ }
35
+
36
+ function setProviderConfig(userId, providerKey, config) {
37
+ const normalizedProviderKey = normalizeProviderKey(providerKey);
38
+ if (!Number.isInteger(Number(userId)) || Number(userId) <= 0 || !normalizedProviderKey) {
39
+ throw new Error('A valid user and provider are required to save integration config.');
40
+ }
41
+
42
+ const payload = config && typeof config === 'object' ? config : {};
43
+ db.prepare(
44
+ `INSERT INTO integration_provider_configs (
45
+ user_id,
46
+ provider_key,
47
+ config_json,
48
+ created_at,
49
+ updated_at
50
+ ) VALUES (?, ?, ?, datetime('now'), datetime('now'))
51
+ ON CONFLICT(user_id, provider_key) DO UPDATE SET
52
+ config_json = excluded.config_json,
53
+ updated_at = excluded.updated_at`,
54
+ ).run(
55
+ Number(userId),
56
+ normalizedProviderKey,
57
+ encryptValue(JSON.stringify(payload)),
58
+ );
59
+ }
60
+
61
+ function deleteProviderConfig(userId, providerKey) {
62
+ const normalizedProviderKey = normalizeProviderKey(providerKey);
63
+ if (!Number.isInteger(Number(userId)) || Number(userId) <= 0 || !normalizedProviderKey) {
64
+ return;
65
+ }
66
+
67
+ db.prepare(
68
+ 'DELETE FROM integration_provider_configs WHERE user_id = ? AND provider_key = ?',
69
+ ).run(Number(userId), normalizedProviderKey);
70
+ }
71
+
72
+ module.exports = {
73
+ deleteProviderConfig,
74
+ getProviderConfig,
75
+ setProviderConfig,
76
+ };
@@ -2,19 +2,29 @@
2
2
 
3
3
  const { createFigmaProvider } = require('./figma/provider');
4
4
  const { createGoogleWorkspaceProvider } = require('./google/provider');
5
+ const { createGithubProvider } = require('./github/provider');
6
+ const { createHomeAssistantProvider } = require('./home_assistant/provider');
5
7
  const { createMicrosoftProvider } = require('./microsoft/provider');
6
8
  const { createNotionProvider } = require('./notion/provider');
9
+ const { createSpotifyProvider } = require('./spotify/provider');
7
10
  const { createSlackProvider } = require('./slack/provider');
11
+ const { createWeatherProvider } = require('./weather/provider');
8
12
  const { createWhatsAppPersonalProvider } = require('./whatsapp');
13
+ const { createCodexProvider } = require('./codex/provider');
9
14
 
10
15
  function createIntegrationRegistry(options = {}) {
11
16
  const providers = [
12
17
  createGoogleWorkspaceProvider(),
18
+ createGithubProvider(),
13
19
  createNotionProvider(),
14
20
  createMicrosoftProvider(),
15
21
  createSlackProvider(),
16
22
  createFigmaProvider(),
23
+ createHomeAssistantProvider(),
24
+ createWeatherProvider(),
25
+ createSpotifyProvider(),
17
26
  createWhatsAppPersonalProvider(options),
27
+ createCodexProvider(),
18
28
  ];
19
29
  const byKey = new Map(providers.map((provider) => [provider.key, provider]));
20
30