fix calendar sync and daily

This commit is contained in:
Arjun 2026-04-01 09:46:08 +05:30
parent 5a354049cc
commit 19b51e974f
4 changed files with 204 additions and 56 deletions

View file

@ -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);

View file

@ -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,
'```',
'',
`<!--task-target:${TARGET_ID}-->`,
`<!--/task-target:${TARGET_ID}-->`,
'',
].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');
}

View file

@ -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 <sender@example.com>","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:

View file

@ -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<string, unknown>;
// Composio may return events in different structures
let events: Array<Record<string, unknown>> = [];
if (Array.isArray(data.items)) {
events = data.items as Array<Record<string, unknown>>;
} else if (Array.isArray(data.events)) {
events = data.events as Array<Record<string, unknown>>;
} else if (data.event_data && typeof data.event_data === 'object') {
const nested = data.event_data as Record<string, unknown>;
if (Array.isArray(nested.event_data)) {
events = nested.event_data as Array<Record<string, unknown>>;
} else if (Array.isArray(data.event_data)) {
events = data.event_data as Array<Record<string, unknown>>;
}
} else if (Array.isArray(data)) {
events = data as unknown as Array<Record<string, unknown>>;
}
const currentEventIds = new Set<string>();
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<string, unknown> = {
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<string, unknown>;
// Composio may return events in different structures
let events: Array<Record<string, unknown>> = [];
if (Array.isArray(data.items)) {
events = data.items as Array<Record<string, unknown>>;
} else if (Array.isArray(data.events)) {
events = data.events as Array<Record<string, unknown>>;
} else if (data.event_data && typeof data.event_data === 'object') {
const nested = data.event_data as Record<string, unknown>;
if (Array.isArray(nested.event_data)) {
events = nested.event_data as Array<Record<string, unknown>>;
} else if (Array.isArray(data.event_data)) {
events = data.event_data as Array<Record<string, unknown>>;
}
} else if (Array.isArray(data)) {
events = data as unknown as Array<Record<string, unknown>>;
}
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