neoagent 2.1.17 → 2.1.18-beta.1

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 CHANGED
@@ -27,6 +27,12 @@ OPENAI_API_KEY=your-openai-api-key-here
27
27
  # Get your key at: https://aistudio.google.com/app/apikey
28
28
  GOOGLE_AI_KEY=your-google-ai-key-here
29
29
 
30
+ # Google Workspace official integration OAuth client
31
+ # Redirect URI should match ${PUBLIC_URL:-http://localhost:3333}/api/integrations/oauth/callback
32
+ GOOGLE_OAUTH_CLIENT_ID=your-google-oauth-client-id
33
+ GOOGLE_OAUTH_CLIENT_SECRET=your-google-oauth-client-secret
34
+ # GOOGLE_OAUTH_REDIRECT_URI=http://localhost:3333/api/integrations/oauth/callback
35
+
30
36
  # Brave Search API key — used for:
31
37
  # • Native web_search tool (search the web without driving the browser)
32
38
  # Get your key at: https://api.search.brave.com/
@@ -25,6 +25,9 @@ At least one API key is required. The active provider and model are configured i
25
25
  | `OPENAI_API_KEY` | GPT-4o / Whisper (OpenAI) |
26
26
  | `XAI_API_KEY` | Grok (xAI) |
27
27
  | `GOOGLE_AI_KEY` | Gemini (Google) |
28
+ | `GOOGLE_OAUTH_CLIENT_ID` | Google Workspace official integrations OAuth client ID |
29
+ | `GOOGLE_OAUTH_CLIENT_SECRET` | Google Workspace official integrations OAuth client secret |
30
+ | `GOOGLE_OAUTH_REDIRECT_URI` | Optional override for the Google Workspace OAuth callback URL |
28
31
  | `MINIMAX_API_KEY` | MiniMax Code (Coding Plan / Token Plan for `MiniMax-M2.7`) |
29
32
  | `BRAVE_SEARCH_API_KEY` | Brave Search API for the native `web_search` tool |
30
33
  | `DEEPGRAM_API_KEY` | Recordings transcription with Deepgram Nova-3 multilingual |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.1.17",
3
+ "version": "2.1.18-beta.1",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -55,6 +55,7 @@
55
55
  "express": "^4.21.2",
56
56
  "express-rate-limit": "^7.5.0",
57
57
  "express-session": "^1.18.1",
58
+ "googleapis": "^150.0.1",
58
59
  "helmet": "^8.0.0",
59
60
  "multer": "^1.4.5-lts.1",
60
61
  "node-cron": "^3.0.3",
@@ -108,6 +108,35 @@ db.exec(`
108
108
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
109
109
  );
110
110
 
111
+ CREATE TABLE IF NOT EXISTS integration_connections (
112
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
113
+ user_id INTEGER NOT NULL,
114
+ provider_key TEXT NOT NULL,
115
+ app_key TEXT NOT NULL DEFAULT 'default',
116
+ status TEXT DEFAULT 'not_connected',
117
+ account_email TEXT,
118
+ scopes_json TEXT DEFAULT '[]',
119
+ credentials_json TEXT DEFAULT '{}',
120
+ metadata_json TEXT DEFAULT '{}',
121
+ last_connected_at TEXT,
122
+ created_at TEXT DEFAULT (datetime('now')),
123
+ updated_at TEXT DEFAULT (datetime('now')),
124
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
125
+ UNIQUE(user_id, provider_key, app_key, account_email)
126
+ );
127
+
128
+ CREATE TABLE IF NOT EXISTS integration_oauth_states (
129
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
130
+ user_id INTEGER NOT NULL,
131
+ provider_key TEXT NOT NULL,
132
+ app_key TEXT NOT NULL DEFAULT 'default',
133
+ state TEXT NOT NULL UNIQUE,
134
+ code_verifier TEXT NOT NULL,
135
+ expires_at TEXT NOT NULL,
136
+ created_at TEXT DEFAULT (datetime('now')),
137
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
138
+ );
139
+
111
140
  CREATE TABLE IF NOT EXISTS scheduled_tasks (
112
141
  id INTEGER PRIMARY KEY AUTOINCREMENT,
113
142
  user_id INTEGER NOT NULL,
@@ -173,6 +202,8 @@ db.exec(`
173
202
  CREATE INDEX IF NOT EXISTS idx_agent_runs_user ON agent_runs(user_id, created_at DESC);
174
203
  CREATE INDEX IF NOT EXISTS idx_agent_runs_status ON agent_runs(status);
175
204
  CREATE INDEX IF NOT EXISTS idx_agent_steps_run ON agent_steps(run_id, step_index);
205
+ CREATE INDEX IF NOT EXISTS idx_integration_connections_user ON integration_connections(user_id, provider_key, app_key);
206
+ CREATE INDEX IF NOT EXISTS idx_integration_oauth_states_state ON integration_oauth_states(state);
176
207
  CREATE INDEX IF NOT EXISTS idx_messages_user ON messages(user_id, created_at DESC);
177
208
  CREATE INDEX IF NOT EXISTS idx_messages_platform ON messages(platform, platform_chat_id);
178
209
  CREATE INDEX IF NOT EXISTS idx_conv_messages ON conversation_messages(conversation_id, created_at);
@@ -424,6 +455,135 @@ for (const col of [
424
455
  try { db.exec(col); } catch { /* column already exists */ }
425
456
  }
426
457
 
458
+ function tableHasColumn(tableName, columnName) {
459
+ try {
460
+ return db
461
+ .prepare(`PRAGMA table_info(${tableName})`)
462
+ .all()
463
+ .some((column) => column.name === columnName);
464
+ } catch {
465
+ return false;
466
+ }
467
+ }
468
+
469
+ function parseIntegrationMetadata(metadataJson) {
470
+ try {
471
+ const parsed = JSON.parse(metadataJson || '{}');
472
+ return parsed && typeof parsed === 'object' ? parsed : {};
473
+ } catch {
474
+ return {};
475
+ }
476
+ }
477
+
478
+ function migrateIntegrationConnectionsTable() {
479
+ if (tableHasColumn('integration_connections', 'app_key')) {
480
+ return;
481
+ }
482
+
483
+ const legacyRows = db
484
+ .prepare('SELECT * FROM integration_connections ORDER BY id ASC')
485
+ .all();
486
+
487
+ db.exec(`
488
+ ALTER TABLE integration_connections RENAME TO integration_connections_legacy;
489
+
490
+ CREATE TABLE integration_connections (
491
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
492
+ user_id INTEGER NOT NULL,
493
+ provider_key TEXT NOT NULL,
494
+ app_key TEXT NOT NULL DEFAULT 'default',
495
+ status TEXT DEFAULT 'not_connected',
496
+ account_email TEXT,
497
+ scopes_json TEXT DEFAULT '[]',
498
+ credentials_json TEXT DEFAULT '{}',
499
+ metadata_json TEXT DEFAULT '{}',
500
+ last_connected_at TEXT,
501
+ created_at TEXT DEFAULT (datetime('now')),
502
+ updated_at TEXT DEFAULT (datetime('now')),
503
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
504
+ UNIQUE(user_id, provider_key, app_key, account_email)
505
+ );
506
+ `);
507
+
508
+ const insert = db.prepare(`
509
+ INSERT OR REPLACE INTO integration_connections (
510
+ user_id,
511
+ provider_key,
512
+ app_key,
513
+ status,
514
+ account_email,
515
+ scopes_json,
516
+ credentials_json,
517
+ metadata_json,
518
+ last_connected_at,
519
+ created_at,
520
+ updated_at
521
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
522
+ `);
523
+
524
+ const legacyGoogleApps = ['gmail', 'calendar', 'drive', 'docs', 'sheets'];
525
+ for (const row of legacyRows) {
526
+ const metadata = parseIntegrationMetadata(row.metadata_json);
527
+ const appIds = Array.isArray(metadata.apps)
528
+ ? metadata.apps
529
+ .map((item) => String(item || '').trim())
530
+ .filter(Boolean)
531
+ : row.provider_key === 'google_workspace'
532
+ ? legacyGoogleApps
533
+ : ['default'];
534
+
535
+ for (const appId of appIds) {
536
+ insert.run(
537
+ row.user_id,
538
+ row.provider_key,
539
+ appId,
540
+ row.status || 'not_connected',
541
+ row.account_email || null,
542
+ row.scopes_json || '[]',
543
+ row.credentials_json || '{}',
544
+ row.metadata_json || '{}',
545
+ row.last_connected_at || null,
546
+ row.created_at || null,
547
+ row.updated_at || null,
548
+ );
549
+ }
550
+ }
551
+
552
+ db.exec(`
553
+ DROP TABLE integration_connections_legacy;
554
+ DROP INDEX IF EXISTS idx_integration_connections_user;
555
+ CREATE INDEX IF NOT EXISTS idx_integration_connections_user
556
+ ON integration_connections(user_id, provider_key, app_key);
557
+ `);
558
+ }
559
+
560
+ function migrateIntegrationOauthStatesTable() {
561
+ if (tableHasColumn('integration_oauth_states', 'app_key')) {
562
+ return;
563
+ }
564
+
565
+ db.exec(`
566
+ ALTER TABLE integration_oauth_states RENAME TO integration_oauth_states_legacy;
567
+
568
+ CREATE TABLE integration_oauth_states (
569
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
570
+ user_id INTEGER NOT NULL,
571
+ provider_key TEXT NOT NULL,
572
+ app_key TEXT NOT NULL DEFAULT 'default',
573
+ state TEXT NOT NULL UNIQUE,
574
+ code_verifier TEXT NOT NULL,
575
+ expires_at TEXT NOT NULL,
576
+ created_at TEXT DEFAULT (datetime('now')),
577
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
578
+ );
579
+
580
+ DROP TABLE integration_oauth_states_legacy;
581
+ `);
582
+ }
583
+
584
+ migrateIntegrationConnectionsTable();
585
+ migrateIntegrationOauthStatesTable();
586
+
427
587
  try {
428
588
  db.exec(`
429
589
  INSERT OR REPLACE INTO conversation_history_fts(rowid, content, role, user_id, agent_run_id)
@@ -10,6 +10,7 @@ const routeRegistry = [
10
10
  { basePath: '/api/agents', modulePath: '../routes/agents' },
11
11
  { basePath: '/api/messaging', modulePath: '../routes/messaging' },
12
12
  { basePath: '/api/mcp', modulePath: '../routes/mcp' },
13
+ { basePath: '/api/integrations', modulePath: '../routes/integrations' },
13
14
  { basePath: '/api/skills', modulePath: '../routes/skills' },
14
15
  { basePath: '/api/store', modulePath: '../routes/store' },
15
16
  { basePath: '/api/memory', modulePath: '../routes/memory' },
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"425cfb54d01a9472b3e81d9e76fd63a4a44cfb
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "816714754" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "3396890130" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });