From 19b51e974fb3550c10688ccd35f6445b0673a7c6 Mon Sep 17 00:00:00 2001 From: Arjun <6592213+arkml@users.noreply.github.com> Date: Wed, 1 Apr 2026 09:46:08 +0530 Subject: [PATCH] fix calendar sync and daily --- apps/x/packages/core/src/config/config.ts | 5 + .../core/src/knowledge/ensure_daily_note.ts | 48 +++++++ .../core/src/knowledge/inline_task_agent.ts | 77 +++++++++++ .../core/src/knowledge/sync_calendar.ts | 130 ++++++++++-------- 4 files changed, 204 insertions(+), 56 deletions(-) create mode 100644 apps/x/packages/core/src/knowledge/ensure_daily_note.ts diff --git a/apps/x/packages/core/src/config/config.ts b/apps/x/packages/core/src/config/config.ts index 2bad01f7..f2bc6c8a 100644 --- a/apps/x/packages/core/src/config/config.ts +++ b/apps/x/packages/core/src/config/config.ts @@ -32,6 +32,11 @@ function ensureDefaultConfigs() { ensureDirs(); ensureDefaultConfigs(); +// Ensure default knowledge files exist +import('../knowledge/ensure_daily_note.js').then(m => m.ensureDailyNote()).catch(err => { + console.error('[DailyNote] Failed to ensure daily note:', err); +}); + // Initialize version history repo (async, fire-and-forget on startup) import('../knowledge/version_history.js').then(m => m.initRepo()).catch(err => { console.error('[VersionHistory] Failed to init repo:', err); diff --git a/apps/x/packages/core/src/knowledge/ensure_daily_note.ts b/apps/x/packages/core/src/knowledge/ensure_daily_note.ts new file mode 100644 index 00000000..55383a6b --- /dev/null +++ b/apps/x/packages/core/src/knowledge/ensure_daily_note.ts @@ -0,0 +1,48 @@ +import path from 'path'; +import fs from 'fs'; +import { WorkDir } from '../config/config.js'; + +const KNOWLEDGE_DIR = path.join(WorkDir, 'knowledge'); +const DAILY_NOTE_PATH = path.join(KNOWLEDGE_DIR, 'Today.md'); +const TARGET_ID = 'dailybrief'; + +function buildDailyNoteContent(): string { + const now = new Date(); + const startDate = now.toISOString(); + const endDate = new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000).toISOString(); + + const instruction = 'Create a daily brief for me'; + + const taskBlock = JSON.stringify({ + instruction, + schedule: { + type: 'cron', + expression: '*/15 * * * *', + startDate, + endDate, + }, + 'schedule-label': 'runs every 15 minutes', + targetId: TARGET_ID, + }); + + return [ + '---', + 'live_note: true', + '---', + '# Today', + '', + '```task', + taskBlock, + '```', + '', + ``, + ``, + '', + ].join('\n'); +} + +export function ensureDailyNote(): void { + if (fs.existsSync(DAILY_NOTE_PATH)) return; + fs.writeFileSync(DAILY_NOTE_PATH, buildDailyNoteContent(), 'utf-8'); + console.log('[DailyNote] Created today.md'); +} diff --git a/apps/x/packages/core/src/knowledge/inline_task_agent.ts b/apps/x/packages/core/src/knowledge/inline_task_agent.ts index 3b5a1577..d90ac229 100644 --- a/apps/x/packages/core/src/knowledge/inline_task_agent.ts +++ b/apps/x/packages/core/src/knowledge/inline_task_agent.ts @@ -74,6 +74,83 @@ Current UTC time: ${nowISO} - For scheduled tasks: output ONLY the two markers (schedule + instruction), nothing else. - Do not modify the original note file — the system handles all insertions. +# Daily Brief + +When the instruction is to "create a daily brief" (or similar), generate a comprehensive daily briefing. + +**IMPORTANT:** All workspace tools (workspace-readdir, workspace-readFile, workspace-grep, etc.) take paths **relative to the workspace root**. Use paths like \`calendar_sync/\`, \`gmail_sync/\`, \`knowledge/\` — NOT absolute paths. + +**IMPORTANT:** Check the current date. If the date has changed since the content was last generated, clear everything and start fresh for the new day. + +## Output structure + +Your output MUST start with the current date as a heading: + +\`## Monday, March 31, 2026\` + +(Use the actual current date in this format: **## Day, Month Date, Year**) + +Then include each section below. + +## Sections to include + +### Calendar +1. Use \`workspace-readdir\` with path \`calendar_sync\` to list files +2. Use \`workspace-readFile\` to read each \`.json\` event file (e.g. \`calendar_sync/eventid123.json\`) +3. Filter for events happening **today** (compare the event's start dateTime or date to the current date) +4. **Always** output a \\\`\\\`\\\`calendar block — even if there are no events today. If no events, output an empty events array: + +\`\`\` +\\\`\\\`\\\`calendar +{"title":"Today's Meetings","events":[],"showJoinButton":false} +\\\`\\\`\\\` +\`\`\` + +If there are events, include them: + +\`\`\` +\\\`\\\`\\\`calendar +{"title":"Today's Meetings","events":[{"summary":"Weekly Sync","start":{"dateTime":"2026-04-01T10:00:00+05:30"},"end":{"dateTime":"2026-04-01T11:00:00+05:30"},"location":"Google Meet","htmlLink":"...","conferenceLink":"..."}],"showJoinButton":true} +\\\`\\\`\\\` +\`\`\` + +5. For each upcoming meeting, add a brief note below the calendar block summarizing what the meeting is about. Use \`workspace-grep\` to search knowledge notes for relevant context about attendees or topics. +6. Do NOT add any text like "No meetings found" — the empty calendar block is sufficient. + +### Emails +1. Use \`workspace-readdir\` with path \`gmail_sync\` to list files (skip \`sync_state.json\` and \`attachments/\`) +2. Use \`workspace-readFile\` to read the email markdown files (e.g. \`gmail_sync/threadid123.md\`) +3. Check the frontmatter \`action\` field — emails with \`action: reply\` or \`action: respond\` need a response +4. For emails needing a response, output \\\`\\\`\\\`email blocks with a \`draft_response\`. Example: + +\`\`\` +\\\`\\\`\\\`email +{"threadId":"abc123","summary":"Payment confirmation","subject":"Google services payment","from":"Sender ","date":"2026-04-01T11:28:39+05:30","latest_email":"Hi, I've made the payment...","draft_response":"Thanks for confirming. I'll update our records."} +\\\`\\\`\\\` +\`\`\` + +5. For other important/recent emails, output \\\`\\\`\\\`email blocks without \`draft_response\` as FYI items +6. Focus on emails from the last 24 hours + +### Yesterday's Summary +- Check yesterday's calendar events from \`calendar_sync/\` for meetings that occurred +- Check emails from yesterday in \`gmail_sync/\` +- Use \`workspace-grep\` to search \`knowledge/\` for any updates from yesterday +- Keep concise — a few bullet points + +### Tasks for Today +- Search through \`knowledge/\` using \`workspace-grep\` and \`workspace-readdir\` for tasks, todos, or action items +- Look for checkbox items (\`- [ ]\`), "TODO", "action item", or similar patterns +- Look at recently updated notes for context on current work +- List relevant items as a markdown checklist + +## Output format +- Start with the date heading as described above +- Use clean markdown with the section headers (## Calendar, ## Emails, etc.) +- Use \\\`\\\`\\\`calendar and \\\`\\\`\\\`email code blocks where specified — these render as interactive UI blocks +- Do NOT add filler text or commentary when sections are empty — just show the empty block +- Keep the overall brief scannable and concise + # Target Regions For recurring/scheduled tasks, the note will contain a **target region** delimited by HTML comment tags: diff --git a/apps/x/packages/core/src/knowledge/sync_calendar.ts b/apps/x/packages/core/src/knowledge/sync_calendar.ts index 0c0a2a55..e6748c26 100644 --- a/apps/x/packages/core/src/knowledge/sync_calendar.ts +++ b/apps/x/packages/core/src/knowledge/sync_calendar.ts @@ -434,72 +434,90 @@ async function performSyncComposio() { }; try { - const result = await executeAction( - 'GOOGLECALENDAR_FIND_EVENT', - { - connected_account_id: connectedAccountId, - user_id: 'rowboat-user', - version: 'latest', - arguments: { - calendar_id: 'primary', - time_min: timeMin, - time_max: timeMax, - single_events: true, - order_by: 'startTime', - }, - } - ); - - if (!result.successful || !result.data) { - console.error('[Calendar] Failed to list events via Composio:', result.error); - return; - } - - const data = result.data as Record; - // Composio may return events in different structures - let events: Array> = []; - - if (Array.isArray(data.items)) { - events = data.items as Array>; - } else if (Array.isArray(data.events)) { - events = data.events as Array>; - } else if (data.event_data && typeof data.event_data === 'object') { - const nested = data.event_data as Record; - if (Array.isArray(nested.event_data)) { - events = nested.event_data as Array>; - } else if (Array.isArray(data.event_data)) { - events = data.event_data as Array>; - } - } else if (Array.isArray(data)) { - events = data as unknown as Array>; - } - const currentEventIds = new Set(); let newCount = 0; let updatedCount = 0; const changedTitles: string[] = []; + let pageToken: string | null = null; + const MAX_PAGES = 20; - if (events.length === 0) { - console.log('[Calendar] No events found in this window.'); - } else { - console.log(`[Calendar] Found ${events.length} events.`); - for (const event of events) { - const eventId = event.id as string | undefined; - if (eventId) { - const saveResult = saveComposioEvent(event, SYNC_DIR); - currentEventIds.add(eventId); + for (let page = 0; page < MAX_PAGES; page++) { + const args: Record = { + calendar_id: 'primary', + time_min: timeMin, + time_max: timeMax, + single_events: true, + order_by: 'startTime', + }; + if (pageToken) { + args.page_token = pageToken; + } - if (saveResult.changed) { - await ensureRun(); - changedTitles.push(saveResult.title); - if (saveResult.isNew) { - newCount++; - } else { - updatedCount++; + const result = await executeAction( + 'GOOGLECALENDAR_FIND_EVENT', + { + connected_account_id: connectedAccountId, + user_id: 'rowboat-user', + version: 'latest', + arguments: args, + } + ); + + if (!result.successful || !result.data) { + console.error('[Calendar] Failed to list events via Composio:', result.error); + return; + } + + const data = result.data as Record; + // Composio may return events in different structures + let events: Array> = []; + + if (Array.isArray(data.items)) { + events = data.items as Array>; + } else if (Array.isArray(data.events)) { + events = data.events as Array>; + } else if (data.event_data && typeof data.event_data === 'object') { + const nested = data.event_data as Record; + if (Array.isArray(nested.event_data)) { + events = nested.event_data as Array>; + } else if (Array.isArray(data.event_data)) { + events = data.event_data as Array>; + } + } else if (Array.isArray(data)) { + events = data as unknown as Array>; + } + + if (events.length === 0 && page === 0) { + console.log('[Calendar] No events found in this window.'); + } else if (events.length > 0) { + console.log(`[Calendar] Page ${page + 1}: found ${events.length} events.`); + for (const event of events) { + const eventId = event.id as string | undefined; + if (eventId) { + const saveResult = saveComposioEvent(event, SYNC_DIR); + currentEventIds.add(eventId); + + if (saveResult.changed) { + await ensureRun(); + changedTitles.push(saveResult.title); + if (saveResult.isNew) { + newCount++; + } else { + updatedCount++; + } } } } } + + // Check for next page + const nextToken = data.nextPageToken as string | undefined; + if (nextToken) { + pageToken = nextToken; + console.log(`[Calendar] Fetching next page...`); + } else { + break; + } } // Clean up events no longer in the window