@agenticmail/enterprise 0.5.145 → 0.5.146

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.
@@ -0,0 +1,49 @@
1
+ import {
2
+ AgentRuntime,
3
+ EmailChannel,
4
+ FollowUpScheduler,
5
+ SessionManager,
6
+ SubAgentManager,
7
+ ToolRegistry,
8
+ callLLM,
9
+ createAgentRuntime,
10
+ createNoopHooks,
11
+ createRuntimeHooks,
12
+ estimateMessageTokens,
13
+ estimateTokens,
14
+ executeTool,
15
+ runAgentLoop,
16
+ toolsToDefinitions
17
+ } from "./chunk-BO2INMG7.js";
18
+ import "./chunk-AQH4DFYV.js";
19
+ import "./chunk-JLSQOQ5L.js";
20
+ import {
21
+ PROVIDER_REGISTRY,
22
+ listAllProviders,
23
+ resolveApiKeyForProvider,
24
+ resolveProvider
25
+ } from "./chunk-67KZYSLU.js";
26
+ import "./chunk-NRF3YRF7.js";
27
+ import "./chunk-TYW5XTOW.js";
28
+ import "./chunk-KFQGP6VL.js";
29
+ export {
30
+ AgentRuntime,
31
+ EmailChannel,
32
+ FollowUpScheduler,
33
+ PROVIDER_REGISTRY,
34
+ SessionManager,
35
+ SubAgentManager,
36
+ ToolRegistry,
37
+ callLLM,
38
+ createAgentRuntime,
39
+ createNoopHooks,
40
+ createRuntimeHooks,
41
+ estimateMessageTokens,
42
+ estimateTokens,
43
+ executeTool,
44
+ listAllProviders,
45
+ resolveApiKeyForProvider,
46
+ resolveProvider,
47
+ runAgentLoop,
48
+ toolsToDefinitions
49
+ };
@@ -0,0 +1,12 @@
1
+ import {
2
+ createServer
3
+ } from "./chunk-Y3Y6JZRR.js";
4
+ import "./chunk-3SMTCIR4.js";
5
+ import "./chunk-JLSQOQ5L.js";
6
+ import "./chunk-RO537U6H.js";
7
+ import "./chunk-DRXMYYKN.js";
8
+ import "./chunk-67KZYSLU.js";
9
+ import "./chunk-KFQGP6VL.js";
10
+ export {
11
+ createServer
12
+ };
@@ -0,0 +1,20 @@
1
+ import {
2
+ promptCompanyInfo,
3
+ promptDatabase,
4
+ promptDeployment,
5
+ promptDomain,
6
+ promptRegistration,
7
+ provision,
8
+ runSetupWizard
9
+ } from "./chunk-I3N7IYKU.js";
10
+ import "./chunk-MHIFVS5L.js";
11
+ import "./chunk-KFQGP6VL.js";
12
+ export {
13
+ promptCompanyInfo,
14
+ promptDatabase,
15
+ promptDeployment,
16
+ promptDomain,
17
+ promptRegistration,
18
+ provision,
19
+ runSetupWizard
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/enterprise",
3
- "version": "0.5.145",
3
+ "version": "0.5.146",
4
4
  "description": "AgenticMail Enterprise — cloud-hosted AI agent identity, email, auth & compliance for organizations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -373,6 +373,78 @@ export function createMeetingLifecycleTools(config: MeetingLifecycleConfig, _opt
373
373
  },
374
374
 
375
375
  // ─── Check Meeting Joinability ─────────────────────
376
+ {
377
+ name: 'meeting_join',
378
+ description: 'Join a video meeting (Google Meet, Zoom, or Teams) by opening the meeting URL in a browser. Works in observer mode on containers (can see screen/chat but no real audio/video). On VMs, full audio/video is available.',
379
+ category: 'utility' as const,
380
+ parameters: {
381
+ type: 'object' as const,
382
+ properties: {
383
+ url: { type: 'string', description: 'Meeting URL (e.g. https://meet.google.com/abc-defg-hij)' },
384
+ platform: { type: 'string', description: 'Meeting platform: google_meet, zoom, teams (auto-detected from URL if omitted)' },
385
+ muted: { type: 'boolean', description: 'Join with mic muted (default: true)' },
386
+ cameraOff: { type: 'boolean', description: 'Join with camera off (default: true)' },
387
+ },
388
+ required: ['url'],
389
+ },
390
+ async execute(_id: string, params: any) {
391
+ const meetUrl = params.url;
392
+ if (!meetUrl) return errorResult('Meeting URL is required');
393
+
394
+ const caps = getCaps();
395
+ const summary = getCapabilitySummary(caps);
396
+
397
+ // Auto-detect platform
398
+ let platform = params.platform || 'unknown';
399
+ if (meetUrl.includes('meet.google.com')) platform = 'google_meet';
400
+ else if (meetUrl.includes('zoom.us') || meetUrl.includes('zoom.com')) platform = 'zoom';
401
+ else if (meetUrl.includes('teams.microsoft.com') || meetUrl.includes('teams.live.com')) platform = 'teams';
402
+
403
+ // Return instructions for the agent to use browser tool
404
+ // The agent has browser tool access and should use it to navigate to the URL
405
+ const isObserverOnly = !caps.canJoinMeetings;
406
+ const instructions: string[] = [];
407
+
408
+ if (platform === 'google_meet') {
409
+ instructions.push(
410
+ `Navigate to: ${meetUrl}`,
411
+ 'Wait for the pre-join screen to load.',
412
+ 'If prompted to sign in, you should already be authenticated via Google OAuth.',
413
+ isObserverOnly
414
+ ? 'You are in OBSERVER mode: camera and mic will show as unavailable. Click "Join now" or "Ask to join" anyway.'
415
+ : 'Toggle mic OFF (Ctrl+D) and camera OFF (Ctrl+E) before joining.',
416
+ 'Click the "Join now" or "Ask to join" button.',
417
+ 'Once in the meeting, you can observe the screen and read the chat.',
418
+ );
419
+ } else if (platform === 'zoom') {
420
+ instructions.push(
421
+ `Navigate to: ${meetUrl}`,
422
+ 'Click "Join from Your Browser" (do NOT install the Zoom client).',
423
+ 'Enter your name if prompted.',
424
+ 'Join with mic and camera off.',
425
+ );
426
+ } else if (platform === 'teams') {
427
+ instructions.push(
428
+ `Navigate to: ${meetUrl}`,
429
+ 'Click "Continue on this browser" (do NOT use the Teams app).',
430
+ 'Enter your name if prompted.',
431
+ 'Join with mic and camera off.',
432
+ );
433
+ } else {
434
+ instructions.push(`Navigate to: ${meetUrl}`, 'Follow the on-screen prompts to join.');
435
+ }
436
+
437
+ return jsonResult({
438
+ status: 'ready_to_join',
439
+ platform,
440
+ url: meetUrl,
441
+ mode: isObserverOnly ? 'observer' : 'full',
442
+ deployment: summary.deployment,
443
+ instructions,
444
+ nextStep: 'Use the browser tool to navigate to the URL and follow the instructions above. Use browser(action="navigate", targetUrl="' + meetUrl + '") then browser(action="snapshot") to see the page.',
445
+ });
446
+ },
447
+ },
376
448
  {
377
449
  name: 'meeting_can_join',
378
450
  description: 'Check if this agent can join a video meeting on the current deployment. Returns capabilities and specific instructions based on what is available.',
package/src/cli-agent.ts CHANGED
@@ -78,9 +78,13 @@ export async function runAgent(_args: string[]) {
78
78
  console.log(` State: ${agent.state}`);
79
79
 
80
80
  // 4. Initialize lifecycle (manages agent state, config decryption)
81
- const { AgentLifecycleManager } = await import('./engine/lifecycle.js');
82
- const lifecycle = new AgentLifecycleManager({ db: engineDb });
83
- await lifecycle.loadFromDb();
81
+ // IMPORTANT: We use the routes.js singleton lifecycle so hooks.ts and this file
82
+ // share the SAME instance. This prevents the "two lifecycle" bug where
83
+ // lifecycle.saveAgent() overwrites usage counters written by routes.lifecycle.recordLLMUsage().
84
+ const routes = await import('./engine/routes.js');
85
+ await routes.lifecycle.setDb(engineDb);
86
+ await routes.lifecycle.loadFromDb();
87
+ const lifecycle = routes.lifecycle; // Use the singleton everywhere
84
88
 
85
89
  const managed = lifecycle.getAgent(AGENT_ID);
86
90
  if (!managed) {
@@ -160,15 +164,12 @@ export async function runAgent(_args: string[]) {
160
164
  await runtime.start();
161
165
  const runtimeApp = runtime.getApp();
162
166
 
163
- // 7b. Initialize shared singletons from routes.js so hooks work in standalone mode
167
+ // 7b. Initialize remaining shared singletons from routes.js so hooks work in standalone mode
168
+ // Note: lifecycle was already initialized in step 4 (we use routes.lifecycle as the single instance)
164
169
  try {
165
- const routes = await import('./engine/routes.js');
166
170
  await routes.permissionEngine.setDb(engineDb);
167
171
  console.log(' Permissions: loaded from DB');
168
- // Initialize lifecycle singleton so recordLLMUsage and other hooks work
169
- await routes.lifecycle.setDb(engineDb);
170
- await routes.lifecycle.loadFromDb();
171
- console.log(' Hooks lifecycle: initialized');
172
+ console.log(' Hooks lifecycle: initialized (shared singleton from step 4)');
172
173
  } catch (permErr: any) {
173
174
  console.warn(` Routes init: failed (${permErr.message}) — some features may not work`);
174
175
  }
@@ -488,6 +489,9 @@ Available tools: gmail_send (to, subject, body) or agenticmail_send (to, subject
488
489
 
489
490
  // 13. Start email inbox polling loop
490
491
  startEmailPolling(AGENT_ID, config, lifecycle, runtime, engineDb, memoryManager);
492
+
493
+ // 14. Start calendar polling loop (checks for upcoming meetings to auto-join)
494
+ startCalendarPolling(AGENT_ID, config, runtime, engineDb, memoryManager);
491
495
  }, 3000);
492
496
  }
493
497
 
@@ -601,11 +605,14 @@ async function startEmailPolling(
601
605
 
602
606
  // Persist processed ID to DB so it survives restarts
603
607
  try {
608
+ const ts = new Date().toISOString();
604
609
  await engineDb.execute(
605
- `INSERT INTO agent_memory (id, agent_id, org_id, category, content, importance, confidence, access_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
606
- [crypto.randomUUID(), agentId, orgId, 'processed_email', envelope.uid, 'low', 1.0, 0, new Date().toISOString(), new Date().toISOString()]
610
+ `INSERT INTO agent_memory (id, agent_id, org_id, category, title, content, source, importance, confidence, access_count, tags, metadata, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`,
611
+ [crypto.randomUUID(), agentId, orgId, 'processed_email', `Processed: ${envelope.subject || envelope.uid}`, envelope.uid, 'system', 'low', 1.0, 0, '[]', '{}', ts, ts]
607
612
  );
608
- } catch {}
613
+ } catch (peErr: any) {
614
+ console.warn(`[email-poll] Failed to persist processed ID: ${peErr.message}`);
615
+ }
609
616
 
610
617
  // Read full message
611
618
  const fullMsg = await emailProvider.readMessage(envelope.uid, 'INBOX');
@@ -759,7 +766,25 @@ EXAMPLES of things to remember:
759
766
  BEFORE starting work, call memory(action: "search", query: "relevant topic") to check if you already know something useful.
760
767
  This avoids repeating work and helps you recall folder IDs, preferences, and context.
761
768
 
762
- The goal: get smarter with every interaction. Never do the same research twice. Never ask for info you've already stored.`;
769
+ The goal: get smarter with every interaction. Never do the same research twice. Never ask for info you've already stored.
770
+
771
+ == CALENDAR & MEETINGS (AUTONOMOUS) ==
772
+ When you receive a calendar invite or meeting link (Google Meet, Zoom, Teams), you MUST:
773
+ 1. Check google_calendar_list for upcoming events in the next 24 hours
774
+ 2. If a meeting is starting soon (within 15 minutes), attempt to join it using the meeting_join tool
775
+ 3. Store the meeting details in your memory (meeting ID, time, link, attendees)
776
+
777
+ For meetings you organize or are invited to:
778
+ - ALWAYS check your calendar autonomously — don't wait to be told
779
+ - If you created a meeting, you are the HOST — join BEFORE other attendees so they can be admitted
780
+ - Use meeting_join to open the meeting link in your browser
781
+ - In observer mode (container deployment): you can see the screen and chat, but cannot send real audio/video
782
+
783
+ When you receive an email containing a meeting link (meet.google.com, zoom.us, teams.microsoft.com):
784
+ - Extract the link and attempt to join immediately if the meeting is now
785
+ - If the meeting is in the future, store it in memory and note the time`;
786
+
787
+
763
788
 
764
789
  const session = await runtime.spawnSession({
765
790
  agentId,
@@ -798,3 +823,135 @@ The goal: get smarter with every interaction. Never do the same research twice.
798
823
  // First poll after 5s
799
824
  setTimeout(pollOnce, 5000);
800
825
  }
826
+
827
+ // ─── Calendar Polling Loop ──────────────────────────────────
828
+
829
+ async function startCalendarPolling(
830
+ agentId: string, config: any, runtime: any,
831
+ engineDb: any, memoryManager: any
832
+ ) {
833
+ const emailConfig = config.emailConfig;
834
+ if (!emailConfig?.oauthAccessToken) {
835
+ console.log('[calendar-poll] No OAuth token, calendar polling disabled');
836
+ return;
837
+ }
838
+
839
+ const providerType = emailConfig.provider || (emailConfig.oauthProvider === 'google' ? 'google' : 'microsoft');
840
+ if (providerType !== 'google') {
841
+ console.log('[calendar-poll] Calendar polling only supports Google for now');
842
+ return;
843
+ }
844
+
845
+ // Token refresh function
846
+ const refreshToken = async (): Promise<string> => {
847
+ const res = await fetch('https://oauth2.googleapis.com/token', {
848
+ method: 'POST',
849
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
850
+ body: new URLSearchParams({
851
+ client_id: emailConfig.oauthClientId,
852
+ client_secret: emailConfig.oauthClientSecret,
853
+ refresh_token: emailConfig.oauthRefreshToken,
854
+ grant_type: 'refresh_token',
855
+ }),
856
+ });
857
+ const data = await res.json() as any;
858
+ if (data.access_token) {
859
+ emailConfig.oauthAccessToken = data.access_token;
860
+ return data.access_token;
861
+ }
862
+ throw new Error('Token refresh failed');
863
+ };
864
+
865
+ const CALENDAR_POLL_INTERVAL = 5 * 60_000; // Check every 5 minutes
866
+ const joinedMeetings = new Set<string>(); // Track already-joined meeting IDs
867
+
868
+ console.log('[calendar-poll] ✅ Calendar polling started (every 5 min)');
869
+
870
+ async function checkCalendar() {
871
+ try {
872
+ let token = emailConfig.oauthAccessToken;
873
+
874
+ // Get events in the next 30 minutes
875
+ const now = new Date();
876
+ const soon = new Date(now.getTime() + 30 * 60_000);
877
+ const params = new URLSearchParams({
878
+ timeMin: now.toISOString(),
879
+ timeMax: soon.toISOString(),
880
+ singleEvents: 'true',
881
+ orderBy: 'startTime',
882
+ maxResults: '10',
883
+ });
884
+
885
+ let res = await fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events?${params}`, {
886
+ headers: { Authorization: `Bearer ${token}` },
887
+ });
888
+
889
+ // Token expired — refresh
890
+ if (res.status === 401) {
891
+ try { token = await refreshToken(); } catch { return; }
892
+ res = await fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events?${params}`, {
893
+ headers: { Authorization: `Bearer ${token}` },
894
+ });
895
+ }
896
+
897
+ if (!res.ok) return;
898
+ const data = await res.json() as any;
899
+ const events = data.items || [];
900
+
901
+ for (const event of events) {
902
+ const meetLink = event.hangoutLink || event.conferenceData?.entryPoints?.find((e: any) => e.entryPointType === 'video')?.uri;
903
+ if (!meetLink) continue;
904
+ if (joinedMeetings.has(event.id)) continue;
905
+
906
+ // Check if meeting starts within 10 minutes
907
+ const startTime = new Date(event.start?.dateTime || event.start?.date);
908
+ const minutesUntilStart = (startTime.getTime() - now.getTime()) / 60_000;
909
+
910
+ if (minutesUntilStart <= 10) {
911
+ console.log(`[calendar-poll] Meeting starting soon: "${event.summary}" in ${Math.round(minutesUntilStart)} min — ${meetLink}`);
912
+ joinedMeetings.add(event.id);
913
+
914
+ // Spawn a session to join the meeting
915
+ const agentName = config.displayName || config.name;
916
+ const role = config.identity?.role || 'AI Agent';
917
+ const identity = config.identity || {};
918
+
919
+ try {
920
+ await runtime.spawnSession({
921
+ agentId,
922
+ message: `[Calendar Alert] You have a meeting starting ${minutesUntilStart <= 0 ? 'NOW' : `in ${Math.round(minutesUntilStart)} minutes`}:
923
+
924
+ Title: ${event.summary || 'Untitled Meeting'}
925
+ Time: ${startTime.toISOString()}
926
+ Link: ${meetLink}
927
+ Attendees: ${(event.attendees || []).map((a: any) => a.email).join(', ') || 'none listed'}
928
+ ${event.description ? `Description: ${event.description.slice(0, 300)}` : ''}
929
+
930
+ Join this meeting now using the meeting_join tool with the link above. You are ${event.organizer?.self ? 'the HOST — join immediately so attendees can be admitted' : 'an attendee'}.`,
931
+ systemPrompt: `You are ${agentName}, a ${role}. ${identity.personality || ''}
932
+
933
+ You have a meeting to join RIGHT NOW. Use the meeting_join tool to open the meeting link in your browser.
934
+
935
+ Steps:
936
+ 1. Call meeting_join with the meeting link
937
+ 2. If that fails, try using the browser tool to navigate to the link directly
938
+ 3. Once in the meeting, monitor the chat and screen
939
+ 4. Store meeting notes in memory after the meeting
940
+
941
+ IMPORTANT: Join the meeting IMMEDIATELY. Do not email anyone about it — just join.`,
942
+ });
943
+ console.log(`[calendar-poll] ✅ Spawned meeting join session for "${event.summary}"`);
944
+ } catch (err: any) {
945
+ console.error(`[calendar-poll] Failed to spawn meeting session: ${err.message}`);
946
+ }
947
+ }
948
+ }
949
+ } catch (err: any) {
950
+ console.error(`[calendar-poll] Error: ${err.message}`);
951
+ }
952
+ }
953
+
954
+ // First check after 10s, then every 5 min
955
+ setTimeout(checkCalendar, 10_000);
956
+ setInterval(checkCalendar, CALENDAR_POLL_INTERVAL);
957
+ }