diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index b65dcc5c..0a3c548e 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -18,6 +18,7 @@ import z from 'zod'; import { RunEvent } from 'packages/shared/dist/runs.js'; import container from '@x/core/dist/di/container.js'; import { IGranolaConfigRepo } from '@x/core/dist/knowledge/granola/repo.js'; +import { triggerSync as triggerGranolaSync } from '@x/core/dist/knowledge/granola/sync.js'; type InvokeChannels = ipc.InvokeChannels; type IPCChannels = ipc.IPCChannels; @@ -316,6 +317,12 @@ export function setupIpcHandlers() { 'granola:setConfig': async (_event, args) => { const repo = container.resolve('granolaConfigRepo'); await repo.setConfig({ enabled: args.enabled }); + + // Trigger sync immediately when enabled + if (args.enabled) { + triggerGranolaSync(); + } + return { success: true }; }, }); diff --git a/apps/x/apps/main/src/oauth-handler.ts b/apps/x/apps/main/src/oauth-handler.ts index 9201bac1..1e471aa9 100644 --- a/apps/x/apps/main/src/oauth-handler.ts +++ b/apps/x/apps/main/src/oauth-handler.ts @@ -6,6 +6,9 @@ import { getProviderConfig, getAvailableProviders } from '@x/core/dist/auth/prov import container from '@x/core/dist/di/container.js'; import { IOAuthRepo } from '@x/core/dist/auth/repo.js'; import { IClientRegistrationRepo } from '@x/core/dist/auth/client-repo.js'; +import { triggerSync as triggerGmailSync } from '@x/core/dist/knowledge/sync_gmail.js'; +import { triggerSync as triggerCalendarSync } from '@x/core/dist/knowledge/sync_calendar.js'; +import { triggerSync as triggerFirefliesSync } from '@x/core/dist/knowledge/sync_fireflies.js'; const REDIRECT_URI = 'http://localhost:8080/oauth/callback'; @@ -138,6 +141,14 @@ export async function connectProvider(provider: string): Promise<{ success: bool // Save tokens console.log(`[OAuth] Token exchange successful for ${provider}`); await oauthRepo.saveTokens(provider, tokens); + + // Trigger immediate sync for relevant providers + if (provider === 'google') { + triggerGmailSync(); + triggerCalendarSync(); + } else if (provider === 'fireflies-ai') { + triggerFirefliesSync(); + } } catch (error) { console.error('OAuth token exchange failed:', error); throw error; diff --git a/apps/x/packages/core/src/knowledge/granola/sync.ts b/apps/x/packages/core/src/knowledge/granola/sync.ts index ee873928..6c736085 100644 --- a/apps/x/packages/core/src/knowledge/granola/sync.ts +++ b/apps/x/packages/core/src/knowledge/granola/sync.ts @@ -23,6 +23,30 @@ const RATE_LIMIT_RETRY_DELAY_MS = 60 * 1000; // Wait 1 minute on rate limit const MAX_RETRIES = 3; // Maximum retries for rate-limited requests const MAX_BATCH_SIZE = 10; // Process max 10 documents per folder per sync +// --- Wake Signal for Immediate Sync Trigger --- +let wakeResolve: (() => void) | null = null; + +export function triggerSync(): void { + if (wakeResolve) { + console.log('[Granola] Triggered - waking up immediately'); + wakeResolve(); + wakeResolve = null; + } +} + +function interruptibleSleep(ms: number): Promise { + return new Promise(resolve => { + const timeout = setTimeout(() => { + wakeResolve = null; + resolve(); + }, ms); + wakeResolve = () => { + clearTimeout(timeout); + resolve(); + }; + }); +} + // --- Token Extraction --- interface WorkosTokens { @@ -404,7 +428,7 @@ async function syncNotes(): Promise { export async function init(): Promise { console.log('[Granola] Starting Granola Sync...'); - console.log(`[Granola] Will check every ${SYNC_INTERVAL_MS / 60000} minutes.`); + console.log(`[Granola] Will sync every ${SYNC_INTERVAL_MS / 60000} minutes.`); console.log(`[Granola] Notes will be saved to: ${SYNC_DIR}`); while (true) { @@ -414,9 +438,9 @@ export async function init(): Promise { console.error('[Granola] Error in sync loop:', error); } - // Sleep before next check + // Sleep before next check (can be interrupted by triggerSync) console.log(`[Granola] Sleeping for ${SYNC_INTERVAL_MS / 60000} minutes...`); - await new Promise(resolve => setTimeout(resolve, SYNC_INTERVAL_MS)); + await interruptibleSleep(SYNC_INTERVAL_MS); } } diff --git a/apps/x/packages/core/src/knowledge/sync_calendar.ts b/apps/x/packages/core/src/knowledge/sync_calendar.ts index 0499b8e7..f2719357 100644 --- a/apps/x/packages/core/src/knowledge/sync_calendar.ts +++ b/apps/x/packages/core/src/knowledge/sync_calendar.ts @@ -17,6 +17,30 @@ const REQUIRED_SCOPES = [ const nhm = new NodeHtmlMarkdown(); +// --- Wake Signal for Immediate Sync Trigger --- +let wakeResolve: (() => void) | null = null; + +export function triggerSync(): void { + if (wakeResolve) { + console.log('[Calendar] Triggered - waking up immediately'); + wakeResolve(); + wakeResolve = null; + } +} + +function interruptibleSleep(ms: number): Promise { + return new Promise(resolve => { + const timeout = setTimeout(() => { + wakeResolve = null; + resolve(); + }, ms); + wakeResolve = () => { + clearTimeout(timeout); + resolve(); + }; + }); +} + // --- Helper Functions --- function cleanFilename(name: string): string { @@ -211,7 +235,7 @@ async function performSync(syncDir: string, lookbackDays: number) { export async function init() { console.log("Starting Google Calendar & Notes Sync (TS)..."); - console.log(`Will check for credentials every ${SYNC_INTERVAL_MS / 1000} seconds.`); + console.log(`Will sync every ${SYNC_INTERVAL_MS / 1000} seconds.`); while (true) { try { @@ -228,8 +252,8 @@ export async function init() { console.error("Error in main loop:", error); } - // Sleep for N minutes before next check + // Sleep for N minutes before next check (can be interrupted by triggerSync) console.log(`Sleeping for ${SYNC_INTERVAL_MS / 1000} seconds...`); - await new Promise(resolve => setTimeout(resolve, SYNC_INTERVAL_MS)); + await interruptibleSleep(SYNC_INTERVAL_MS); } } \ No newline at end of file diff --git a/apps/x/packages/core/src/knowledge/sync_fireflies.ts b/apps/x/packages/core/src/knowledge/sync_fireflies.ts index bb74eaf2..e65529f6 100644 --- a/apps/x/packages/core/src/knowledge/sync_fireflies.ts +++ b/apps/x/packages/core/src/knowledge/sync_fireflies.ts @@ -12,6 +12,30 @@ const API_DELAY_MS = 2000; // 2 second delay between API calls const RATE_LIMIT_RETRY_DELAY_MS = 60 * 1000; // Wait 1 minute on rate limit const MAX_RETRIES = 3; // Maximum retries for rate-limited requests +// --- Wake Signal for Immediate Sync Trigger --- +let wakeResolve: (() => void) | null = null; + +export function triggerSync(): void { + if (wakeResolve) { + console.log('[Fireflies] Triggered - waking up immediately'); + wakeResolve(); + wakeResolve = null; + } +} + +function interruptibleSleep(ms: number): Promise { + return new Promise(resolve => { + const timeout = setTimeout(() => { + wakeResolve = null; + resolve(); + }, ms); + wakeResolve = () => { + clearTimeout(timeout); + resolve(); + }; + }); +} + // --- Types for Fireflies API responses --- interface FirefliesMeeting { @@ -553,7 +577,7 @@ async function syncMeetings() { */ export async function init() { console.log('[Fireflies] Starting Fireflies Sync...'); - console.log(`[Fireflies] Will check for credentials every ${SYNC_INTERVAL_MS / 1000} seconds.`); + console.log(`[Fireflies] Will sync every ${SYNC_INTERVAL_MS / 1000} seconds.`); console.log(`[Fireflies] Syncing transcripts from the last ${LOOKBACK_DAYS} days.`); while (true) { @@ -571,9 +595,9 @@ export async function init() { console.error('[Fireflies] Error in main loop:', error); } - // Sleep before next check + // Sleep before next check (can be interrupted by triggerSync) console.log(`[Fireflies] Sleeping for ${SYNC_INTERVAL_MS / 1000} seconds...`); - await new Promise(resolve => setTimeout(resolve, SYNC_INTERVAL_MS)); + await interruptibleSleep(SYNC_INTERVAL_MS); } } diff --git a/apps/x/packages/core/src/knowledge/sync_gmail.ts b/apps/x/packages/core/src/knowledge/sync_gmail.ts index ecf8a4e8..d1782a96 100644 --- a/apps/x/packages/core/src/knowledge/sync_gmail.ts +++ b/apps/x/packages/core/src/knowledge/sync_gmail.ts @@ -13,6 +13,30 @@ const REQUIRED_SCOPE = 'https://www.googleapis.com/auth/gmail.readonly'; const nhm = new NodeHtmlMarkdown(); +// --- Wake Signal for Immediate Sync Trigger --- +let wakeResolve: (() => void) | null = null; + +export function triggerSync(): void { + if (wakeResolve) { + console.log('[Gmail] Triggered - waking up immediately'); + wakeResolve(); + wakeResolve = null; + } +} + +function interruptibleSleep(ms: number): Promise { + return new Promise(resolve => { + const timeout = setTimeout(() => { + wakeResolve = null; + resolve(); + }, ms); + wakeResolve = () => { + clearTimeout(timeout); + resolve(); + }; + }); +} + // --- Helper Functions --- function cleanFilename(name: string): string { @@ -287,7 +311,7 @@ async function performSync() { export async function init() { console.log("Starting Gmail Sync (TS)..."); - console.log(`Will check for credentials every ${SYNC_INTERVAL_MS / 1000} seconds.`); + console.log(`Will sync every ${SYNC_INTERVAL_MS / 1000} seconds.`); while (true) { try { @@ -304,8 +328,8 @@ export async function init() { console.error("Error in main loop:", error); } - // Sleep for N minutes before next check + // Sleep for N minutes before next check (can be interrupted by triggerSync) console.log(`Sleeping for ${SYNC_INTERVAL_MS / 1000} seconds...`); - await new Promise(resolve => setTimeout(resolve, SYNC_INTERVAL_MS)); + await interruptibleSleep(SYNC_INTERVAL_MS); } }