@agenticmail/enterprise 0.5.312 → 0.5.313
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/README.md +682 -544
- package/dist/agent-autonomy-PSXQ4MNP.js +766 -0
- package/dist/agent-heartbeat-6H3YAQ32.js +510 -0
- package/dist/agent-heartbeat-7WS3XILF.js +510 -0
- package/dist/agent-heartbeat-BFGKYUUK.js +510 -0
- package/dist/agent-heartbeat-SSV65YTX.js +510 -0
- package/dist/agent-heartbeat-T5IIHVF4.js +510 -0
- package/dist/agent-heartbeat-X3C6FIU2.js +510 -0
- package/dist/agent-tools-BW6CLQQ7.js +13897 -0
- package/dist/agent-tools-KEA7QEWF.js +13897 -0
- package/dist/agent-tools-NU7V3S5N.js +13899 -0
- package/dist/agent-tools-WINDYRQ2.js +13897 -0
- package/dist/chunk-3ELH5CU6.js +4910 -0
- package/dist/chunk-4QYRS3MS.js +1519 -0
- package/dist/chunk-52REEVDW.js +1519 -0
- package/dist/chunk-5RZJ76YI.js +4977 -0
- package/dist/chunk-6L7FQI5Q.js +4909 -0
- package/dist/chunk-763OMGFI.js +1519 -0
- package/dist/chunk-7ILSXGY6.js +1519 -0
- package/dist/chunk-7UCKD25B.js +551 -0
- package/dist/chunk-A6MSR7DL.js +4977 -0
- package/dist/chunk-ASD2YB6O.js +1519 -0
- package/dist/chunk-AZOIHLLX.js +4977 -0
- package/dist/chunk-BDCFOP7O.js +537 -0
- package/dist/chunk-BSVWPG6I.js +106 -0
- package/dist/chunk-C46DNDZB.js +1519 -0
- package/dist/chunk-CFVTK4FQ.js +4977 -0
- package/dist/chunk-CHJAOKCJ.js +4921 -0
- package/dist/chunk-CYEWTXYH.js +4977 -0
- package/dist/chunk-D3KFSWLK.js +48 -0
- package/dist/chunk-DUVGNAIY.js +4977 -0
- package/dist/chunk-DX4XEFVE.js +25229 -0
- package/dist/chunk-EX6FQSEV.js +167 -0
- package/dist/chunk-F5VZ5EUH.js +1519 -0
- package/dist/chunk-FVUDSPOD.js +4977 -0
- package/dist/chunk-G6FTZKJX.js +4977 -0
- package/dist/chunk-GFEAZN6Y.js +1519 -0
- package/dist/chunk-HKV4FQFW.js +1519 -0
- package/dist/chunk-ICCPULDT.js +25217 -0
- package/dist/chunk-IYEM627Q.js +25216 -0
- package/dist/chunk-JHRJ4QJ6.js +1519 -0
- package/dist/chunk-K2DAUYHV.js +4977 -0
- package/dist/chunk-KDQDSZZQ.js +4973 -0
- package/dist/chunk-LDUD6AZY.js +1519 -0
- package/dist/chunk-LES5TJ5L.js +4909 -0
- package/dist/chunk-MJGGW6MC.js +106 -0
- package/dist/chunk-MQKIWAHQ.js +106 -0
- package/dist/chunk-NGA7BBPF.js +48 -0
- package/dist/chunk-OE3TI4IQ.js +1519 -0
- package/dist/chunk-OHSBIYDR.js +4977 -0
- package/dist/chunk-OZEYDEPB.js +1519 -0
- package/dist/chunk-P4PODSQH.js +1519 -0
- package/dist/chunk-P7UOSFIE.js +636 -0
- package/dist/chunk-PFN6DODU.js +4921 -0
- package/dist/chunk-PKDVM4IY.js +4917 -0
- package/dist/chunk-Q5KG3G7U.js +25115 -0
- package/dist/chunk-QMVNW4FJ.js +25229 -0
- package/dist/chunk-QZ5UPRBE.js +4977 -0
- package/dist/chunk-SPP23N42.js +4977 -0
- package/dist/chunk-SRGHNFOY.js +4921 -0
- package/dist/chunk-TPLVQFXM.js +2594 -0
- package/dist/chunk-U3XYF4QP.js +4977 -0
- package/dist/chunk-VRRJH2DY.js +4921 -0
- package/dist/chunk-WY42BS3F.js +1519 -0
- package/dist/chunk-XAA4VHHZ.js +1519 -0
- package/dist/chunk-Z5Y5KTPC.js +4977 -0
- package/dist/chunk-ZA4QRACH.js +4977 -0
- package/dist/chunk-ZHLGSTXF.js +4909 -0
- package/dist/cli-agent-26BUULHZ.js +2169 -0
- package/dist/cli-agent-2FLJWXOC.js +2169 -0
- package/dist/cli-agent-4NNQFLO6.js +2255 -0
- package/dist/cli-agent-5WV3EEPW.js +2252 -0
- package/dist/cli-agent-65JUT6DU.js +2193 -0
- package/dist/cli-agent-6HLL7A5K.js +2255 -0
- package/dist/cli-agent-CZ26QWUZ.js +2210 -0
- package/dist/cli-agent-HPVSWDNQ.js +2255 -0
- package/dist/cli-agent-K4SBVG5X.js +2210 -0
- package/dist/cli-agent-K5D424X2.js +2252 -0
- package/dist/cli-agent-U4OL5FGK.js +2210 -0
- package/dist/cli-agent-WUMPOIKQ.js +2169 -0
- package/dist/cli-agent-WWRGGJ2F.js +2255 -0
- package/dist/cli-agent-ZDBBTVGU.js +2193 -0
- package/dist/cli-agent-ZIZ5JP4O.js +2252 -0
- package/dist/cli-recover-I4KNR2OI.js +487 -0
- package/dist/cli-recover-IQTUKWR2.js +487 -0
- package/dist/cli-recover-OYJHELOR.js +487 -0
- package/dist/cli-recover-PVQC7UXB.js +487 -0
- package/dist/cli-recover-T32NABFA.js +487 -0
- package/dist/cli-serve-FTQJ3RUK.js +143 -0
- package/dist/cli-serve-G4PUCASH.js +143 -0
- package/dist/cli-serve-HBZYUUQ3.js +143 -0
- package/dist/cli-serve-L3NUROMO.js +143 -0
- package/dist/cli-serve-LAA5WIZK.js +143 -0
- package/dist/cli-serve-LV4TUSJD.js +143 -0
- package/dist/cli-serve-MFCTVA2L.js +140 -0
- package/dist/cli-serve-QCRUFI5B.js +143 -0
- package/dist/cli-serve-S7OGQN4P.js +143 -0
- package/dist/cli-serve-SI4BQRXT.js +140 -0
- package/dist/cli-serve-UNB7EHN4.js +143 -0
- package/dist/cli-serve-UV3GVDRD.js +143 -0
- package/dist/cli-serve-V5QICXR5.js +143 -0
- package/dist/cli-serve-VG6Z6GIB.js +143 -0
- package/dist/cli-serve-XSYHPGZI.js +143 -0
- package/dist/cli-serve-Y534FCRV.js +140 -0
- package/dist/cli-verify-CZIITRED.js +149 -0
- package/dist/cli-verify-N73GOKEF.js +149 -0
- package/dist/cli-verify-QEEBZOUZ.js +149 -0
- package/dist/cli-verify-RC5HI6DU.js +149 -0
- package/dist/cli-verify-VKBNIEAX.js +149 -0
- package/dist/cli.js +5 -5
- package/dist/dashboard/app.js +8 -2
- package/dist/dashboard/components/org-switcher.js +5 -1
- package/dist/dashboard/org-switcher.js +156 -0
- package/dist/dashboard/pages/login.js +160 -4
- package/dist/dashboard/pages/task-pipeline.js +1 -1
- package/dist/factory-3IWXVE37.js +9 -0
- package/dist/factory-5M6PTMLC.js +11 -0
- package/dist/factory-CSSHN7GE.js +11 -0
- package/dist/factory-JFWXTAWK.js +11 -0
- package/dist/factory-TBGUYM5X.js +9 -0
- package/dist/google-W5AYGNUJ.js +33 -0
- package/dist/index.js +6 -6
- package/dist/meetings-FJ453ENF.js +12 -0
- package/dist/postgres-BCHZWRU3.js +832 -0
- package/dist/postgres-BI4QVRM6.js +825 -0
- package/dist/postgres-BOTHOPDW.js +875 -0
- package/dist/postgres-JBUKR3TA.js +873 -0
- package/dist/postgres-Z7QYSU6K.js +861 -0
- package/dist/routes-7QYAQTWA.js +90 -0
- package/dist/routes-JCBVZU54.js +90 -0
- package/dist/routes-KEDEJFRE.js +90 -0
- package/dist/routes-WI64ADVH.js +90 -0
- package/dist/routes-X36OSCID.js +90 -0
- package/dist/runtime-75XR6KEW.js +45 -0
- package/dist/runtime-BNM7ZNNL.js +45 -0
- package/dist/runtime-ES6WCJ7D.js +45 -0
- package/dist/runtime-KYJTML2B.js +45 -0
- package/dist/runtime-LO67ZHQA.js +45 -0
- package/dist/runtime-VIXKKVSZ.js +45 -0
- package/dist/runtime-WHWJPCGK.js +45 -0
- package/dist/runtime-Z2Q6GUHH.js +45 -0
- package/dist/runtime-ZZ6CALSB.js +45 -0
- package/dist/server-27A4WEJC.js +28 -0
- package/dist/server-2CBXP4WS.js +28 -0
- package/dist/server-4JQAB5R4.js +28 -0
- package/dist/server-6BOM5U64.js +28 -0
- package/dist/server-FLJKNPRD.js +28 -0
- package/dist/server-HMIHIQ2N.js +28 -0
- package/dist/server-KIXXLR2D.js +28 -0
- package/dist/server-KSEIZTXF.js +28 -0
- package/dist/server-MPVW7DKZ.js +28 -0
- package/dist/server-PRTVRQ2D.js +28 -0
- package/dist/server-SYIG6HAX.js +28 -0
- package/dist/server-U32KDIXC.js +28 -0
- package/dist/server-WFN6CA4T.js +28 -0
- package/dist/server-XQUE3FGT.js +28 -0
- package/dist/server-XWT2UORK.js +28 -0
- package/dist/server-Y3BGNN5Q.js +28 -0
- package/dist/setup-352L2TPS.js +20 -0
- package/dist/setup-4MM645XK.js +20 -0
- package/dist/setup-5JPWW6IP.js +20 -0
- package/dist/setup-CUN6LVUV.js +20 -0
- package/dist/setup-D3YHPWPY.js +20 -0
- package/dist/setup-D4A5I6UM.js +20 -0
- package/dist/setup-DOPLXTB3.js +20 -0
- package/dist/setup-E3NSIM6B.js +20 -0
- package/dist/setup-E3V2D7NL.js +20 -0
- package/dist/setup-FSYPGI2C.js +20 -0
- package/dist/setup-G3RPKRG3.js +20 -0
- package/dist/setup-KJ77HNWK.js +20 -0
- package/dist/setup-LPSOY5V5.js +20 -0
- package/dist/setup-N3ODDSQE.js +20 -0
- package/dist/setup-NLDM3M2P.js +20 -0
- package/dist/setup-SWJMNDWF.js +20 -0
- package/dist/system-prompts-6OUTAMH6.js +41 -0
- package/dist/task-queue-YP2I54IA.js +9 -0
- package/dist/telegram-QRNGRT5M.js +17 -0
- package/dist/whatsapp-VYVINCGV.js +31 -0
- package/god_is_great.html +35 -0
- package/package.json +1 -1
- package/src/agent-tools/index.ts +4 -1
- package/src/agent-tools/tool-resolver.ts +15 -4
- package/src/agent-tools/tools/browser.ts +2 -2
- package/src/agent-tools/tools/local/dependency-manager.ts +286 -0
- package/src/agent-tools/tools/local/index.ts +3 -0
- package/src/agent-tools/tools/messaging/telegram.ts +29 -0
- package/src/agent-tools/tools/messaging/whatsapp.ts +59 -4
- package/src/auth/routes.ts +1 -1
- package/src/cli-agent.ts +47 -6
- package/src/cli-serve.ts +2 -5
- package/src/dashboard/app.js +8 -2
- package/src/dashboard/components/org-switcher.js +5 -1
- package/src/dashboard/pages/login.js +160 -4
- package/src/dashboard/pages/task-pipeline.js +1 -1
- package/src/db/adapter.ts +2 -0
- package/src/db/factory.ts +78 -0
- package/src/db/postgres.ts +57 -12
- package/src/engine/agent-autonomy.ts +1 -1
- package/src/engine/agent-heartbeat.ts +1 -1
- package/src/engine/messaging-poller.ts +146 -11
- package/src/engine/oauth-connect-routes.ts +23 -3
- package/src/engine/routes.ts +1 -1
- package/src/engine/task-poller.ts +54 -3
- package/src/engine/task-queue.ts +30 -0
- package/src/runtime/index.ts +2 -1
- package/src/runtime/types.ts +2 -0
- package/src/server.ts +13 -1
- package/src/system-prompts/triage.ts +1 -1
|
@@ -382,6 +382,118 @@ export function OnboardingWizard({ onComplete }) {
|
|
|
382
382
|
|
|
383
383
|
var set = function(k, v) { setForm(function(f) { return Object.assign({}, f, { [k]: v }); }); };
|
|
384
384
|
|
|
385
|
+
// ─── Smart DB URL Analysis & Auto-Optimization ─────
|
|
386
|
+
|
|
387
|
+
var analyzeDbUrl = function(url) {
|
|
388
|
+
if (!url) return null;
|
|
389
|
+
try {
|
|
390
|
+
var u = new URL(url);
|
|
391
|
+
var port = u.port || '5432';
|
|
392
|
+
var host = u.hostname || '';
|
|
393
|
+
var info = {
|
|
394
|
+
host: host, port: port, provider: null, isPooler: false, poolerMode: null,
|
|
395
|
+
directUrl: null, optimizedUrl: null, warnings: [], tips: [], autoFixed: []
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// ── Supabase Detection ──────────────────────────
|
|
399
|
+
if (host.includes('.supabase.co') || host.includes('pooler.supabase.com')) {
|
|
400
|
+
info.provider = 'supabase';
|
|
401
|
+
var projectRef = u.username.replace('postgres.', '');
|
|
402
|
+
|
|
403
|
+
if (host.includes('pooler.supabase.com')) {
|
|
404
|
+
info.isPooler = true;
|
|
405
|
+
info.poolerMode = port === '6543' ? 'transaction' : port === '5432' ? 'session' : 'unknown';
|
|
406
|
+
|
|
407
|
+
// Build direct URL: db.{ref}.supabase.co:5432
|
|
408
|
+
var directU = new URL(url);
|
|
409
|
+
directU.hostname = 'db.' + projectRef + '.supabase.co';
|
|
410
|
+
directU.port = '5432';
|
|
411
|
+
directU.searchParams.delete('pgbouncer');
|
|
412
|
+
info.directUrl = directU.toString();
|
|
413
|
+
|
|
414
|
+
if (port === '5432') {
|
|
415
|
+
// Auto-fix: switch from session mode (5432) to transaction mode (6543)
|
|
416
|
+
var fixedU = new URL(url);
|
|
417
|
+
fixedU.port = '6543';
|
|
418
|
+
fixedU.searchParams.set('pgbouncer', 'true');
|
|
419
|
+
info.optimizedUrl = fixedU.toString();
|
|
420
|
+
info.autoFixed.push('Switched from session mode (port 5432) to transaction mode (port 6543) — higher connection limits and better for multi-process setups.');
|
|
421
|
+
info.autoFixed.push('Added ?pgbouncer=true for proper connection pooling.');
|
|
422
|
+
} else if (port === '6543') {
|
|
423
|
+
// Already on transaction mode — ensure pgbouncer param is set
|
|
424
|
+
if (!u.searchParams.get('pgbouncer')) {
|
|
425
|
+
var optU = new URL(url);
|
|
426
|
+
optU.searchParams.set('pgbouncer', 'true');
|
|
427
|
+
info.optimizedUrl = optU.toString();
|
|
428
|
+
info.autoFixed.push('Added ?pgbouncer=true parameter for proper PgBouncer transaction mode handling.');
|
|
429
|
+
}
|
|
430
|
+
info.tips.push('Transaction mode pooler detected — optimal for production.');
|
|
431
|
+
}
|
|
432
|
+
info.tips.push('Direct URL auto-generated for migrations (bypasses pooler for DDL operations).');
|
|
433
|
+
|
|
434
|
+
} else if (host.startsWith('db.') || host.includes('.supabase.co')) {
|
|
435
|
+
// Direct connection — build pooler URL for them
|
|
436
|
+
info.directUrl = url;
|
|
437
|
+
var region = host.match(/db\.([^.]+)\.supabase\.co/)?.[1] || projectRef;
|
|
438
|
+
// Try to detect region from hostname pattern
|
|
439
|
+
var poolerU = new URL(url);
|
|
440
|
+
// Supabase pooler format: aws-0-{region}.pooler.supabase.com
|
|
441
|
+
poolerU.hostname = 'aws-0-us-east-1.pooler.supabase.com'; // default, user may need to adjust
|
|
442
|
+
poolerU.port = '6543';
|
|
443
|
+
poolerU.username = 'postgres.' + region;
|
|
444
|
+
poolerU.searchParams.set('pgbouncer', 'true');
|
|
445
|
+
info.warnings.push('Direct connection detected. For production with multiple agents, use the Supabase connection pooler.');
|
|
446
|
+
info.tips.push('Go to Supabase Dashboard > Settings > Database > Connection string > URI, and select "Transaction mode" to get the correct pooler URL.');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// ── Neon Detection ──────────────────────────────
|
|
450
|
+
else if (host.includes('.neon.tech')) {
|
|
451
|
+
info.provider = 'neon';
|
|
452
|
+
info.isPooler = host.includes('-pooler');
|
|
453
|
+
if (!info.isPooler) {
|
|
454
|
+
// Auto-fix: add -pooler to hostname
|
|
455
|
+
var neonFixedU = new URL(url);
|
|
456
|
+
var parts = neonFixedU.hostname.split('.');
|
|
457
|
+
if (parts[0] && !parts[0].endsWith('-pooler')) {
|
|
458
|
+
parts[0] = parts[0] + '-pooler';
|
|
459
|
+
neonFixedU.hostname = parts.join('.');
|
|
460
|
+
info.optimizedUrl = neonFixedU.toString();
|
|
461
|
+
info.autoFixed.push('Added connection pooler endpoint (-pooler) for better connection handling.');
|
|
462
|
+
}
|
|
463
|
+
// Direct URL is the original
|
|
464
|
+
info.directUrl = url;
|
|
465
|
+
info.tips.push('Direct URL saved for migrations.');
|
|
466
|
+
} else {
|
|
467
|
+
// Already pooled — generate direct URL
|
|
468
|
+
var neonDirectU = new URL(url);
|
|
469
|
+
var neonParts = neonDirectU.hostname.split('.');
|
|
470
|
+
if (neonParts[0]) {
|
|
471
|
+
neonParts[0] = neonParts[0].replace(/-pooler$/, '');
|
|
472
|
+
neonDirectU.hostname = neonParts.join('.');
|
|
473
|
+
}
|
|
474
|
+
info.directUrl = neonDirectU.toString();
|
|
475
|
+
info.tips.push('Neon pooler detected — optimal for production. Direct URL auto-generated for migrations.');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// ── Generic Postgres ────────────────────────────
|
|
479
|
+
else {
|
|
480
|
+
info.provider = 'postgres';
|
|
481
|
+
// Check for common PgBouncer indicators
|
|
482
|
+
if (port === '6432' || port === '6543' || u.searchParams.get('pgbouncer') === 'true') {
|
|
483
|
+
info.isPooler = true;
|
|
484
|
+
info.poolerMode = 'transaction';
|
|
485
|
+
info.tips.push('PgBouncer detected. Connection pooling will be configured automatically.');
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return info;
|
|
490
|
+
} catch { return null; }
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
var dbUrlInfo = useMemo(function() {
|
|
494
|
+
return analyzeDbUrl(form.dbConnectionString);
|
|
495
|
+
}, [form.dbConnectionString]);
|
|
496
|
+
|
|
385
497
|
// ─── DB Config Builder ──────────────────────────────
|
|
386
498
|
|
|
387
499
|
var buildDbConfig = function() {
|
|
@@ -389,7 +501,17 @@ export function OnboardingWizard({ onComplete }) {
|
|
|
389
501
|
if (t === 'sqlite') return { type: 'sqlite' };
|
|
390
502
|
if (t === 'turso') return { type: 'turso', connectionString: form.dbConnectionString, authToken: form.dbAuthToken };
|
|
391
503
|
if (t === 'dynamodb') return { type: 'dynamodb', region: form.dbRegion, accessKeyId: form.dbAccessKey, secretAccessKey: form.dbSecretKey };
|
|
392
|
-
|
|
504
|
+
// Use optimized URL if available (auto-fixed pooler mode, pgbouncer param, etc.)
|
|
505
|
+
var connStr = (dbUrlInfo && dbUrlInfo.optimizedUrl) ? dbUrlInfo.optimizedUrl : form.dbConnectionString;
|
|
506
|
+
var config = { type: t, connectionString: connStr };
|
|
507
|
+
// Auto-attach smart DB metadata for Postgres-family databases
|
|
508
|
+
if (dbUrlInfo) {
|
|
509
|
+
config.poolerDetected = dbUrlInfo.isPooler || !!dbUrlInfo.optimizedUrl;
|
|
510
|
+
config.poolerMode = dbUrlInfo.poolerMode || (dbUrlInfo.optimizedUrl ? 'transaction' : null);
|
|
511
|
+
config.directUrl = dbUrlInfo.directUrl;
|
|
512
|
+
config.provider = dbUrlInfo.provider;
|
|
513
|
+
}
|
|
514
|
+
return config;
|
|
393
515
|
};
|
|
394
516
|
|
|
395
517
|
// ─── Actions ────────────────────────────────────────
|
|
@@ -528,9 +650,43 @@ export function OnboardingWizard({ onComplete }) {
|
|
|
528
650
|
);
|
|
529
651
|
}
|
|
530
652
|
// Connection string types: postgres, mysql, mongodb, supabase, neon, planetscale, cockroachdb
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
h('
|
|
653
|
+
var urlHints = dbUrlInfo && form.dbConnectionString.length > 10;
|
|
654
|
+
return h(Fragment, null,
|
|
655
|
+
h('div', { className: 'form-group' },
|
|
656
|
+
h('label', { className: 'form-label' }, 'Connection String'),
|
|
657
|
+
h('input', { className: 'input', value: form.dbConnectionString, onChange: function(e) { set('dbConnectionString', e.target.value); }, placeholder: currentDbType ? currentDbType.placeholder : '' })
|
|
658
|
+
),
|
|
659
|
+
// Smart URL analysis hints
|
|
660
|
+
urlHints && h('div', { style: { margin: '-8px 0 12px', fontSize: 12, lineHeight: 1.6 } },
|
|
661
|
+
dbUrlInfo.provider && dbUrlInfo.provider !== 'postgres' && h('div', { style: { color: 'var(--accent)', fontWeight: 500, marginBottom: 4 } },
|
|
662
|
+
dbUrlInfo.provider === 'supabase' ? '\uD83D\uDFE2 Supabase' : dbUrlInfo.provider === 'neon' ? '\uD83D\uDFE2 Neon' : dbUrlInfo.provider,
|
|
663
|
+
' detected',
|
|
664
|
+
dbUrlInfo.isPooler || dbUrlInfo.optimizedUrl ? ' \u2014 connection will be auto-optimized' : ' (direct connection)'
|
|
665
|
+
),
|
|
666
|
+
// Auto-fix notifications (green — things we fixed for them)
|
|
667
|
+
dbUrlInfo.autoFixed && dbUrlInfo.autoFixed.map(function(f, i) {
|
|
668
|
+
return h('div', { key: 'f' + i, style: { color: '#10b981', padding: '4px 8px', background: 'rgba(16,185,129,0.08)', borderRadius: 6, marginBottom: 4 } },
|
|
669
|
+
'\u2728 Auto-configured: ', f
|
|
670
|
+
);
|
|
671
|
+
}),
|
|
672
|
+
// Warnings (yellow — things they need to act on)
|
|
673
|
+
dbUrlInfo.warnings.map(function(w, i) {
|
|
674
|
+
return h('div', { key: 'w' + i, style: { color: 'var(--warning, #f59e0b)', padding: '4px 8px', background: 'rgba(245,158,11,0.1)', borderRadius: 6, marginBottom: 4 } },
|
|
675
|
+
'\u26A0\uFE0F ', w
|
|
676
|
+
);
|
|
677
|
+
}),
|
|
678
|
+
// Tips (informational)
|
|
679
|
+
dbUrlInfo.tips.map(function(t, i) {
|
|
680
|
+
return h('div', { key: 't' + i, style: { color: 'var(--text-secondary)', padding: '2px 0' } },
|
|
681
|
+
'\u2714\uFE0F ', t
|
|
682
|
+
);
|
|
683
|
+
}),
|
|
684
|
+
// Summary of what will be sent
|
|
685
|
+
(dbUrlInfo.directUrl || dbUrlInfo.optimizedUrl) && h('div', { style: { color: 'var(--text-secondary)', padding: '6px 8px', background: 'var(--bg-secondary)', borderRadius: 6, marginTop: 4, fontSize: 11 } },
|
|
686
|
+
dbUrlInfo.optimizedUrl && h('div', null, '\uD83D\uDD17 Pooler URL: ', h('code', { style: { fontSize: 10 } }, dbUrlInfo.optimizedUrl.substring(0, 60) + '...')),
|
|
687
|
+
dbUrlInfo.directUrl && h('div', null, '\uD83D\uDD17 Direct URL (migrations): ', h('code', { style: { fontSize: 10 } }, dbUrlInfo.directUrl.substring(0, 60) + '...'))
|
|
688
|
+
)
|
|
689
|
+
)
|
|
534
690
|
);
|
|
535
691
|
};
|
|
536
692
|
|
|
@@ -231,6 +231,7 @@ var ACTIVITY_TYPE_COLORS = {
|
|
|
231
231
|
created: '#6366f1', assigned: '#991b1b', started: '#06b6d4', in_progress: '#06b6d4',
|
|
232
232
|
completed: '#15803d', failed: '#ef4444', cancelled: '#6b7394', delegated: '#a855f7',
|
|
233
233
|
compaction: '#8b5cf6', error: '#ef4444',
|
|
234
|
+
crash: '#dc2626', recovery: '#f59e0b', note: '#3b82f6',
|
|
234
235
|
};
|
|
235
236
|
|
|
236
237
|
function ActivityLog(props) {
|
|
@@ -1084,7 +1085,6 @@ export function AgentTaskPipeline(props) {
|
|
|
1084
1085
|
}
|
|
1085
1086
|
|
|
1086
1087
|
return h(Fragment, null,
|
|
1087
|
-
h(orgCtx.Switcher),
|
|
1088
1088
|
active.length > 0 && h('div', { style: { marginBottom: 12 } },
|
|
1089
1089
|
h('div', { style: { fontSize: 11, fontWeight: 600, color: STATUS_COLORS.in_progress, marginBottom: 6, display: 'flex', alignItems: 'center', gap: 4 } },
|
|
1090
1090
|
h('div', { style: { width: 6, height: 6, borderRadius: '50%', background: STATUS_COLORS.in_progress, animation: 'flowPulse 2s infinite' } }),
|
package/src/db/adapter.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface DatabaseConfig {
|
|
|
28
28
|
secretAccessKey?: string;
|
|
29
29
|
/** Turso-specific */
|
|
30
30
|
authToken?: string;
|
|
31
|
+
/** Direct connection URL (bypasses PgBouncer) — used for migrations */
|
|
32
|
+
directUrl?: string;
|
|
31
33
|
/** Extra driver options */
|
|
32
34
|
options?: Record<string, unknown>;
|
|
33
35
|
}
|
package/src/db/factory.ts
CHANGED
|
@@ -35,6 +35,84 @@ export async function createAdapter(config: DatabaseConfig): Promise<DatabaseAda
|
|
|
35
35
|
return adapter;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Smart config builder: auto-detects Supabase/Neon pooler URLs,
|
|
40
|
+
* sets optimal connection params, and derives a directUrl for migrations.
|
|
41
|
+
* Call this when building config from a raw DATABASE_URL env var.
|
|
42
|
+
*/
|
|
43
|
+
export function smartDbConfig(connectionString: string, typeHint?: DatabaseType): DatabaseConfig {
|
|
44
|
+
const type: DatabaseType = typeHint || (connectionString.startsWith('postgres') ? 'postgres' : 'sqlite');
|
|
45
|
+
const config: DatabaseConfig = { type, connectionString };
|
|
46
|
+
|
|
47
|
+
if (type === 'sqlite') return config;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const u = new URL(connectionString);
|
|
51
|
+
const host = u.hostname || '';
|
|
52
|
+
const port = u.port || '5432';
|
|
53
|
+
|
|
54
|
+
// ── Supabase detection ──────────────────────────
|
|
55
|
+
if (host.includes('pooler.supabase.com') || host.includes('.supabase.co')) {
|
|
56
|
+
const projectRef = u.username.replace('postgres.', '');
|
|
57
|
+
|
|
58
|
+
if (host.includes('pooler.supabase.com')) {
|
|
59
|
+
// Generate direct URL for migrations
|
|
60
|
+
const directU = new URL(connectionString);
|
|
61
|
+
directU.hostname = 'db.' + projectRef + '.supabase.co';
|
|
62
|
+
directU.port = '5432';
|
|
63
|
+
directU.searchParams.delete('pgbouncer');
|
|
64
|
+
config.directUrl = directU.toString();
|
|
65
|
+
|
|
66
|
+
// Auto-switch to transaction mode if on session mode
|
|
67
|
+
if (port === '5432') {
|
|
68
|
+
const fixedU = new URL(connectionString);
|
|
69
|
+
fixedU.port = '6543';
|
|
70
|
+
fixedU.searchParams.set('pgbouncer', 'true');
|
|
71
|
+
config.connectionString = fixedU.toString();
|
|
72
|
+
console.log('[db] Auto-optimized: Supabase session mode (5432) → transaction mode (6543)');
|
|
73
|
+
} else if (!u.searchParams.get('pgbouncer')) {
|
|
74
|
+
const fixedU = new URL(connectionString);
|
|
75
|
+
fixedU.searchParams.set('pgbouncer', 'true');
|
|
76
|
+
config.connectionString = fixedU.toString();
|
|
77
|
+
console.log('[db] Auto-configured: Added ?pgbouncer=true for Supabase transaction mode');
|
|
78
|
+
}
|
|
79
|
+
} else if (host.startsWith('db.')) {
|
|
80
|
+
// Direct connection — save as directUrl, but warn
|
|
81
|
+
config.directUrl = connectionString;
|
|
82
|
+
console.log('[db] Supabase direct connection detected. For production, use the pooler URL (port 6543).');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// ── Neon detection ──────────────────────────────
|
|
86
|
+
else if (host.includes('.neon.tech')) {
|
|
87
|
+
if (!host.includes('-pooler')) {
|
|
88
|
+
// Direct endpoint — save as directUrl, generate pooler
|
|
89
|
+
config.directUrl = connectionString;
|
|
90
|
+
const poolerU = new URL(connectionString);
|
|
91
|
+
const parts = poolerU.hostname.split('.');
|
|
92
|
+
if (parts[0] && !parts[0].endsWith('-pooler')) {
|
|
93
|
+
parts[0] = parts[0] + '-pooler';
|
|
94
|
+
poolerU.hostname = parts.join('.');
|
|
95
|
+
config.connectionString = poolerU.toString();
|
|
96
|
+
console.log('[db] Auto-optimized: Neon direct → pooler endpoint');
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// Pooler endpoint — derive direct URL
|
|
100
|
+
const directU = new URL(connectionString);
|
|
101
|
+
const parts = directU.hostname.split('.');
|
|
102
|
+
if (parts[0]) {
|
|
103
|
+
parts[0] = parts[0].replace(/-pooler$/, '');
|
|
104
|
+
directU.hostname = parts.join('.');
|
|
105
|
+
}
|
|
106
|
+
config.directUrl = directU.toString();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// Invalid URL — just pass through as-is
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return config;
|
|
114
|
+
}
|
|
115
|
+
|
|
38
116
|
export function getSupportedDatabases(): { type: DatabaseType; label: string; group: string }[] {
|
|
39
117
|
return [
|
|
40
118
|
{ type: 'postgres', label: 'PostgreSQL', group: 'SQL' },
|
package/src/db/postgres.ts
CHANGED
|
@@ -31,16 +31,27 @@ export class PostgresAdapter extends DatabaseAdapter {
|
|
|
31
31
|
readonly type = 'postgres' as const;
|
|
32
32
|
private pool: any = null;
|
|
33
33
|
private ended = false;
|
|
34
|
+
private _directUrl?: string;
|
|
35
|
+
private _connectionString?: string;
|
|
36
|
+
private _isPgBouncer = false;
|
|
34
37
|
|
|
35
38
|
async connect(config: DatabaseConfig): Promise<void> {
|
|
36
39
|
this.ended = false;
|
|
37
40
|
const { Pool } = await getPg();
|
|
38
41
|
|
|
39
42
|
// ── Smart pool sizing ──────────────────────────────
|
|
40
|
-
// Detect if using PgBouncer/Supabase pooler
|
|
41
|
-
|
|
42
|
-
const port = config.port || (
|
|
43
|
-
const
|
|
43
|
+
// Detect if using PgBouncer/Supabase pooler from port, hostname, or query params
|
|
44
|
+
const connUrl = config.connectionString ? new URL(config.connectionString) : null;
|
|
45
|
+
const port = config.port || (connUrl ? connUrl.port : '5432');
|
|
46
|
+
const hostname = connUrl?.hostname || config.host || '';
|
|
47
|
+
const isPgBouncer = String(port) === '6543' || String(port) === '5433'
|
|
48
|
+
|| hostname.includes('.pooler.supabase.') || hostname.includes('.pooler.')
|
|
49
|
+
|| connUrl?.searchParams.get('pgbouncer') === 'true';
|
|
50
|
+
|
|
51
|
+
// Store for migrations and reconnection
|
|
52
|
+
this._directUrl = config.directUrl || undefined;
|
|
53
|
+
this._connectionString = config.connectionString;
|
|
54
|
+
this._isPgBouncer = isPgBouncer;
|
|
44
55
|
|
|
45
56
|
// Pool size logic:
|
|
46
57
|
// - PgBouncer (Supabase pooler): conservative — shared across processes
|
|
@@ -49,11 +60,11 @@ export class PostgresAdapter extends DatabaseAdapter {
|
|
|
49
60
|
// - Direct connection: more generous
|
|
50
61
|
// - Override via DB_POOL_MAX env var
|
|
51
62
|
const envMax = process.env.DB_POOL_MAX ? parseInt(process.env.DB_POOL_MAX, 10) : 0;
|
|
52
|
-
const defaultMax = isPgBouncer ?
|
|
63
|
+
const defaultMax = isPgBouncer ? 3 : 10;
|
|
53
64
|
const poolMax = envMax || defaultMax;
|
|
54
65
|
|
|
55
66
|
// PgBouncer transaction mode: short idle timeout to release connections fast
|
|
56
|
-
const idleTimeout = isPgBouncer ?
|
|
67
|
+
const idleTimeout = isPgBouncer ? 2000 : 30000;
|
|
57
68
|
|
|
58
69
|
this.pool = new Pool({
|
|
59
70
|
connectionString: config.connectionString,
|
|
@@ -105,16 +116,21 @@ export class PostgresAdapter extends DatabaseAdapter {
|
|
|
105
116
|
* admin shutdown (57P01), connection refused, client already released.
|
|
106
117
|
*/
|
|
107
118
|
private async _query(sql: string, params?: any[]): Promise<any> {
|
|
108
|
-
const RETRYABLE = new Set(['XX000', '53300', '57P01', 'ECONNREFUSED', 'ECONNRESET', '57P03']);
|
|
109
|
-
for (let attempt = 0; attempt <
|
|
119
|
+
const RETRYABLE = new Set(['XX000', '53300', '57P01', 'ECONNREFUSED', 'ECONNRESET', '57P03', '25P02']);
|
|
120
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
110
121
|
try {
|
|
111
122
|
return await this.pool.query(sql, params);
|
|
112
123
|
} catch (err: any) {
|
|
113
124
|
const code = err.code || '';
|
|
114
125
|
const msg = err.message || '';
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
const isAbortedTx = code === '25P02' || msg.includes('current transaction is aborted');
|
|
127
|
+
const isRetryable = RETRYABLE.has(code) || msg.includes('remaining connection') || msg.includes('terminating connection') || msg.includes('Connection terminated') || msg.includes('MaxClientsInSessionMode');
|
|
128
|
+
if ((isRetryable || isAbortedTx) && attempt < 2) {
|
|
129
|
+
// For aborted transaction state, try to reset the connection by issuing ROLLBACK
|
|
130
|
+
if (isAbortedTx) {
|
|
131
|
+
try { await this.pool.query('ROLLBACK'); } catch {}
|
|
132
|
+
}
|
|
133
|
+
await new Promise(r => setTimeout(r, 500 + Math.random() * 1000));
|
|
118
134
|
continue;
|
|
119
135
|
}
|
|
120
136
|
throw err;
|
|
@@ -154,7 +170,33 @@ export class PostgresAdapter extends DatabaseAdapter {
|
|
|
154
170
|
|
|
155
171
|
async migrate(): Promise<void> {
|
|
156
172
|
const stmts = getAllCreateStatements();
|
|
157
|
-
|
|
173
|
+
|
|
174
|
+
// For PgBouncer setups, try direct URL for migrations (DDL needs real transactions)
|
|
175
|
+
// Falls back to pooler connection if direct URL is unreachable
|
|
176
|
+
let directPool: any = null;
|
|
177
|
+
let client: any;
|
|
178
|
+
if (this._isPgBouncer && this._directUrl) {
|
|
179
|
+
try {
|
|
180
|
+
const { Pool } = await getPg();
|
|
181
|
+
directPool = new Pool({
|
|
182
|
+
connectionString: this._directUrl,
|
|
183
|
+
ssl: { rejectUnauthorized: false },
|
|
184
|
+
max: 1,
|
|
185
|
+
idleTimeoutMillis: 5000,
|
|
186
|
+
connectionTimeoutMillis: 8000,
|
|
187
|
+
});
|
|
188
|
+
directPool.on('error', () => {}); // Suppress stderr noise during fallback
|
|
189
|
+
client = await directPool.connect();
|
|
190
|
+
console.log('[postgres] Using direct connection for migrations (bypassing PgBouncer)');
|
|
191
|
+
} catch (err: any) {
|
|
192
|
+
console.warn(`[postgres] Direct connection unavailable (${err.message?.slice(0, 80)}), using pooler for migrations`);
|
|
193
|
+
if (directPool) { try { await directPool.end(); } catch {} }
|
|
194
|
+
directPool = null;
|
|
195
|
+
client = await this.pool.connect();
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
client = await this.pool.connect();
|
|
199
|
+
}
|
|
158
200
|
try {
|
|
159
201
|
await client.query('BEGIN');
|
|
160
202
|
for (const stmt of stmts) {
|
|
@@ -247,6 +289,9 @@ export class PostgresAdapter extends DatabaseAdapter {
|
|
|
247
289
|
throw err;
|
|
248
290
|
} finally {
|
|
249
291
|
client.release();
|
|
292
|
+
if (directPool) {
|
|
293
|
+
try { await directPool.end(); } catch {}
|
|
294
|
+
}
|
|
250
295
|
}
|
|
251
296
|
}
|
|
252
297
|
|
|
@@ -360,7 +360,7 @@ export class AgentAutonomyManager {
|
|
|
360
360
|
- ${failedChats} chat message(s) may be unanswered
|
|
361
361
|
|
|
362
362
|
Your morning routine:
|
|
363
|
-
1. Check your inbox with
|
|
363
|
+
1. Check your inbox with gmail_search (unread only) — scan subjects and senders
|
|
364
364
|
2. For each important email, create a Google Task: google_tasks_create with title, notes, and priority
|
|
365
365
|
3. Check Google Chat for any unanswered messages: google_chat_list_messages
|
|
366
366
|
4. For any failed sessions that look important, add them as tasks too
|
|
@@ -693,7 +693,7 @@ export class AgentHeartbeatManager {
|
|
|
693
693
|
${summaries}
|
|
694
694
|
|
|
695
695
|
For each item, take the appropriate action:
|
|
696
|
-
- For unread emails: Check your inbox with
|
|
696
|
+
- For unread emails: Check your inbox with gmail_search and respond to any urgent ones.
|
|
697
697
|
- For upcoming events: Check google_calendar_list for the next 2 hours and prepare.
|
|
698
698
|
- For stale sessions: Review and close any stuck sessions.
|
|
699
699
|
- For unanswered chats: Check Google Chat and respond.
|
|
@@ -35,6 +35,7 @@ interface AgentEndpoint {
|
|
|
35
35
|
displayName: string;
|
|
36
36
|
host: string;
|
|
37
37
|
port: number;
|
|
38
|
+
dataDir?: string;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export class MessagingPoller {
|
|
@@ -108,7 +109,8 @@ export class MessagingPoller {
|
|
|
108
109
|
var agTgToken = chanCfg?.telegram?.botToken;
|
|
109
110
|
if (agTgToken) {
|
|
110
111
|
tgToken = agTgToken;
|
|
111
|
-
|
|
112
|
+
var _dataDir2 = process.env.DATA_DIR || '/tmp/agenticmail-data';
|
|
113
|
+
defaultAgent = { id: ag2.id, displayName: ag2.displayName || ag2.name || 'Agent', host: ag2.host || 'localhost', port: ag2.port || 3100, dataDir: `${_dataDir2}/agents/${ag2.id}` };
|
|
112
114
|
break;
|
|
113
115
|
}
|
|
114
116
|
}
|
|
@@ -186,6 +188,12 @@ export class MessagingPoller {
|
|
|
186
188
|
try {
|
|
187
189
|
var { onWhatsAppMessage } = await import('../agent-tools/tools/messaging/whatsapp.js');
|
|
188
190
|
var unsub = onWhatsAppMessage(agent.id, (msg) => {
|
|
191
|
+
// Build mediaFiles array for multimodal processing
|
|
192
|
+
var waMediaFiles: Array<{ path: string; type: string; mimeType?: string }> | undefined;
|
|
193
|
+
if (msg.mediaPath && msg.mediaType) {
|
|
194
|
+
var waMime = msg.mediaType === 'photo' ? 'image/jpeg' : msg.mediaType === 'video' ? 'video/mp4' : msg.mediaType === 'audio' ? 'audio/ogg' : 'application/octet-stream';
|
|
195
|
+
waMediaFiles = [{ path: msg.mediaPath, type: msg.mediaType, mimeType: waMime }];
|
|
196
|
+
}
|
|
189
197
|
this.dispatch(agent, {
|
|
190
198
|
source: 'whatsapp',
|
|
191
199
|
senderName: msg.pushName || (msg.from?.includes('@') ? msg.from.split('@')[0] : msg.from) || 'Unknown',
|
|
@@ -194,6 +202,7 @@ export class MessagingPoller {
|
|
|
194
202
|
isGroup: msg.isGroup,
|
|
195
203
|
messageId: msg.messageId,
|
|
196
204
|
isSelfChat: msg.isSelfChat,
|
|
205
|
+
mediaFiles: waMediaFiles,
|
|
197
206
|
});
|
|
198
207
|
});
|
|
199
208
|
this.cleanups.push(unsub);
|
|
@@ -302,16 +311,140 @@ export class MessagingPoller {
|
|
|
302
311
|
|
|
303
312
|
private handleTelegramUpdate(update: any, agent: AgentEndpoint) {
|
|
304
313
|
var msg = update.message;
|
|
305
|
-
if (!msg
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
314
|
+
if (!msg) return;
|
|
315
|
+
|
|
316
|
+
// Extract text — could be text, caption, or empty (media-only)
|
|
317
|
+
var text = msg.text || msg.caption || '';
|
|
318
|
+
|
|
319
|
+
// Detect media
|
|
320
|
+
var mediaType: string | undefined;
|
|
321
|
+
var fileId: string | undefined;
|
|
322
|
+
var fileName: string | undefined;
|
|
323
|
+
var mimeType: string | undefined;
|
|
324
|
+
|
|
325
|
+
if (msg.photo && msg.photo.length > 0) {
|
|
326
|
+
// photo is an array of PhotoSize, pick the largest
|
|
327
|
+
mediaType = 'photo';
|
|
328
|
+
fileId = msg.photo[msg.photo.length - 1].file_id;
|
|
329
|
+
} else if (msg.video) {
|
|
330
|
+
mediaType = 'video';
|
|
331
|
+
fileId = msg.video.file_id;
|
|
332
|
+
fileName = msg.video.file_name;
|
|
333
|
+
mimeType = msg.video.mime_type;
|
|
334
|
+
} else if (msg.document) {
|
|
335
|
+
mediaType = 'document';
|
|
336
|
+
fileId = msg.document.file_id;
|
|
337
|
+
fileName = msg.document.file_name;
|
|
338
|
+
mimeType = msg.document.mime_type;
|
|
339
|
+
} else if (msg.audio) {
|
|
340
|
+
mediaType = 'audio';
|
|
341
|
+
fileId = msg.audio.file_id;
|
|
342
|
+
fileName = msg.audio.file_name;
|
|
343
|
+
mimeType = msg.audio.mime_type;
|
|
344
|
+
} else if (msg.voice) {
|
|
345
|
+
mediaType = 'voice';
|
|
346
|
+
fileId = msg.voice.file_id;
|
|
347
|
+
mimeType = msg.voice.mime_type;
|
|
348
|
+
} else if (msg.video_note) {
|
|
349
|
+
mediaType = 'video_note';
|
|
350
|
+
fileId = msg.video_note.file_id;
|
|
351
|
+
} else if (msg.sticker) {
|
|
352
|
+
mediaType = 'sticker';
|
|
353
|
+
fileId = msg.sticker.file_id;
|
|
354
|
+
text = text || `[Sticker: ${msg.sticker.emoji || ''}]`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Skip if no text AND no media
|
|
358
|
+
if (!text && !fileId) return;
|
|
359
|
+
|
|
360
|
+
// If we have media, download it asynchronously and build a description
|
|
361
|
+
if (fileId) {
|
|
362
|
+
this.downloadTelegramMedia(agent, fileId, mediaType!, fileName, mimeType).then((mediaInfo) => {
|
|
363
|
+
var mediaText = text;
|
|
364
|
+
var mediaFiles: Array<{ path: string; type: string; mimeType?: string }> | undefined;
|
|
365
|
+
if (mediaInfo?.localPath) {
|
|
366
|
+
var desc = `[${mediaType}${fileName ? ': ' + fileName : ''}] saved to: ${mediaInfo.localPath}`;
|
|
367
|
+
mediaText = text ? `${text}\n\n${desc}` : desc;
|
|
368
|
+
// Include media files for multimodal processing
|
|
369
|
+
var detectedMime = mimeType || (mediaType === 'photo' ? 'image/jpeg' : mediaType === 'video' ? 'video/mp4' : 'application/octet-stream');
|
|
370
|
+
mediaFiles = [{ path: mediaInfo.localPath, type: mediaType!, mimeType: detectedMime }];
|
|
371
|
+
} else if (!text) {
|
|
372
|
+
mediaText = `[${mediaType} received${fileName ? ': ' + fileName : ''}]`;
|
|
373
|
+
}
|
|
374
|
+
this.dispatch(agent, {
|
|
375
|
+
source: 'telegram',
|
|
376
|
+
senderName: [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(' ') || 'Unknown',
|
|
377
|
+
senderId: String(msg.from?.id || ''),
|
|
378
|
+
messageText: mediaText,
|
|
379
|
+
isGroup: msg.chat?.type === 'group' || msg.chat?.type === 'supergroup',
|
|
380
|
+
chatId: String(msg.chat?.id),
|
|
381
|
+
messageId: String(msg.message_id),
|
|
382
|
+
mediaFiles,
|
|
383
|
+
});
|
|
384
|
+
}).catch((dlErr) => {
|
|
385
|
+
console.error(`[messaging] Telegram media download failed:`, dlErr?.message || dlErr);
|
|
386
|
+
// Fallback: dispatch with just text description
|
|
387
|
+
this.dispatch(agent, {
|
|
388
|
+
source: 'telegram',
|
|
389
|
+
senderName: [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(' ') || 'Unknown',
|
|
390
|
+
senderId: String(msg.from?.id || ''),
|
|
391
|
+
messageText: text || `[${mediaType} received — download failed]`,
|
|
392
|
+
isGroup: msg.chat?.type === 'group' || msg.chat?.type === 'supergroup',
|
|
393
|
+
chatId: String(msg.chat?.id),
|
|
394
|
+
messageId: String(msg.message_id),
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
} else {
|
|
398
|
+
// Text-only message
|
|
399
|
+
this.dispatch(agent, {
|
|
400
|
+
source: 'telegram',
|
|
401
|
+
senderName: [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(' ') || 'Unknown',
|
|
402
|
+
senderId: String(msg.from?.id || ''),
|
|
403
|
+
messageText: text,
|
|
404
|
+
isGroup: msg.chat?.type === 'group' || msg.chat?.type === 'supergroup',
|
|
405
|
+
chatId: String(msg.chat?.id),
|
|
406
|
+
messageId: String(msg.message_id),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private async downloadTelegramMedia(agent: AgentEndpoint, fileId: string, mediaType: string, fileName?: string, mimeType?: string): Promise<{ localPath: string } | null> {
|
|
412
|
+
try {
|
|
413
|
+
var channelConfig = this.config.getAgentChannelConfig(agent.id);
|
|
414
|
+
var botToken = channelConfig?.telegram?.botToken;
|
|
415
|
+
if (!botToken) return null;
|
|
416
|
+
|
|
417
|
+
// Get file path from Telegram
|
|
418
|
+
var fileRes = await fetch(`https://api.telegram.org/bot${botToken}/getFile?file_id=${fileId}`);
|
|
419
|
+
if (!fileRes.ok) return null;
|
|
420
|
+
var fileData = await fileRes.json() as any;
|
|
421
|
+
var filePath = fileData.result?.file_path;
|
|
422
|
+
if (!filePath) return null;
|
|
423
|
+
|
|
424
|
+
// Download the file
|
|
425
|
+
var downloadUrl = `https://api.telegram.org/file/bot${botToken}/${filePath}`;
|
|
426
|
+
var response = await fetch(downloadUrl);
|
|
427
|
+
if (!response.ok) return null;
|
|
428
|
+
|
|
429
|
+
// Determine local path
|
|
430
|
+
var { join, dirname } = await import('path');
|
|
431
|
+
var { mkdirSync, writeFileSync } = await import('fs');
|
|
432
|
+
var mediaDir = join(agent.dataDir || `/tmp/agents/${agent.id}`, 'media');
|
|
433
|
+
try { mkdirSync(mediaDir, { recursive: true }); } catch {}
|
|
434
|
+
|
|
435
|
+
var ext = filePath.split('.').pop() || (mediaType === 'photo' ? 'jpg' : 'bin');
|
|
436
|
+
var localName = fileName || `${mediaType}-${Date.now()}.${ext}`;
|
|
437
|
+
var localPath = join(mediaDir, localName);
|
|
438
|
+
|
|
439
|
+
var buffer = Buffer.from(await response.arrayBuffer());
|
|
440
|
+
writeFileSync(localPath, buffer);
|
|
441
|
+
|
|
442
|
+
console.log(`[messaging] Telegram media downloaded: ${localPath} (${buffer.length} bytes)`);
|
|
443
|
+
return { localPath };
|
|
444
|
+
} catch (err: any) {
|
|
445
|
+
console.error(`[messaging] Telegram media download failed: ${err.message}`);
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
315
448
|
}
|
|
316
449
|
|
|
317
450
|
// ─── Trust / Authorization ─────────────────────────
|
|
@@ -369,6 +502,7 @@ export class MessagingPoller {
|
|
|
369
502
|
source: string; senderName: string; senderId: string;
|
|
370
503
|
messageText: string; isGroup?: boolean; chatId?: string; messageId?: string;
|
|
371
504
|
isSelfChat?: boolean;
|
|
505
|
+
mediaFiles?: Array<{ path: string; type: string; mimeType?: string }>;
|
|
372
506
|
}) {
|
|
373
507
|
// Self-chat is always trusted (owner messaging themselves to talk to agent)
|
|
374
508
|
if (ctx.isSelfChat) {
|
|
@@ -470,6 +604,7 @@ export class MessagingPoller {
|
|
|
470
604
|
isCustomer: isCustomer,
|
|
471
605
|
customerSystemPrompt: isCustomer ? bizCfg.customerSystemPrompt : undefined,
|
|
472
606
|
restrictTools: isCustomer && bizCfg.restrictCustomerTools !== false,
|
|
607
|
+
...(ctx.mediaFiles && ctx.mediaFiles.length > 0 ? { mediaFiles: ctx.mediaFiles } : {}),
|
|
473
608
|
}),
|
|
474
609
|
signal: AbortSignal.timeout(trust.isManager ? 30000 : 10000), // Manager gets longer timeout
|
|
475
610
|
});
|