diff --git a/apps/x/apps/main/src/main.ts b/apps/x/apps/main/src/main.ts index 68cef1f2..f5580a34 100644 --- a/apps/x/apps/main/src/main.ts +++ b/apps/x/apps/main/src/main.ts @@ -8,6 +8,7 @@ import { init as initCalendarSync } from "@x/core/dist/knowledge/sync_calendar.j import { init as initFirefliesSync } from "@x/core/dist/knowledge/sync_fireflies.js"; import { init as initGranolaSync } from "@x/core/dist/knowledge/granola/sync.js"; import { init as initGraphBuilder } from "@x/core/dist/knowledge/build_graph.js"; +import { init as initPreBuiltRunner } from "@x/core/dist/pre_built/runner.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -62,6 +63,9 @@ app.whenReady().then(() => { // start knowledge graph builder initGraphBuilder(); + // start pre-built agent runner + initPreBuiltRunner(); + app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); diff --git a/apps/x/packages/core/package.json b/apps/x/packages/core/package.json index d660eeaa..fccc2f7a 100644 --- a/apps/x/packages/core/package.json +++ b/apps/x/packages/core/package.json @@ -5,7 +5,7 @@ "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { - "build": "rm -rf dist && tsc && mkdir -p dist/knowledge && cp src/knowledge/note_creation.md dist/knowledge/", + "build": "rm -rf dist && tsc && mkdir -p dist/knowledge dist/pre_built && cp src/knowledge/note_creation.md dist/knowledge/ && cp src/pre_built/*.md dist/pre_built/", "dev": "tsc -w" }, "dependencies": { diff --git a/apps/x/packages/core/src/agents/runtime.ts b/apps/x/packages/core/src/agents/runtime.ts index ddc05816..27862262 100644 --- a/apps/x/packages/core/src/agents/runtime.ts +++ b/apps/x/packages/core/src/agents/runtime.ts @@ -245,15 +245,20 @@ export async function loadAgent(id: string): Promise> { return CopilotAgent; } - // Special case: load note_creation agent from checked-in file - if (id === "note_creation") { + // Special case: load built-in agents from checked-in files + const builtinAgents: Record = { + 'note_creation': '../knowledge/note_creation.md', + 'meeting-prep': '../pre_built/meeting-prep.md', + 'email-draft': '../pre_built/email-draft.md', + }; + + if (id in builtinAgents) { const currentDir = path.dirname(new URL(import.meta.url).pathname); - // File is copied to dist/knowledge during build - const agentFilePath = path.join(currentDir, "../knowledge/note_creation.md"); + const agentFilePath = path.join(currentDir, builtinAgents[id]); const raw = fs.readFileSync(agentFilePath, "utf8"); let agent: z.infer = { - name: "note_creation", + name: id, instructions: raw, }; diff --git a/apps/x/packages/core/src/pre_built/config.ts b/apps/x/packages/core/src/pre_built/config.ts new file mode 100644 index 00000000..1012b7d3 --- /dev/null +++ b/apps/x/packages/core/src/pre_built/config.ts @@ -0,0 +1,138 @@ +import fs from 'fs'; +import path from 'path'; +import { WorkDir } from '../config/config.js'; +import { + PreBuiltConfig, + PreBuiltState, + PreBuiltAgentConfig, + UserConfig, + PREBUILT_AGENTS, +} from './types.js'; + +const CONFIG_PATH = path.join(WorkDir, 'config', 'prebuilt.json'); +const STATE_PATH = path.join(WorkDir, 'pre-built', 'runner_state.json'); +const USER_CONFIG_PATH = path.join(WorkDir, 'config', 'user.json'); + +function ensureDir(dirPath: string): void { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +// --- Config Management --- + +export function getDefaultConfig(): PreBuiltConfig { + const agents: Record = {}; + for (const agentName of PREBUILT_AGENTS) { + agents[agentName] = { + enabled: false, + intervalMs: 5 * 60 * 1000, // 5 minutes + }; + } + return { agents }; +} + +export function loadConfig(): PreBuiltConfig { + try { + if (fs.existsSync(CONFIG_PATH)) { + const content = fs.readFileSync(CONFIG_PATH, 'utf-8'); + const parsed = JSON.parse(content); + return PreBuiltConfig.parse(parsed); + } + } catch (error) { + console.error('[PreBuilt] Error loading config:', error); + } + return getDefaultConfig(); +} + +export function saveConfig(config: PreBuiltConfig): void { + ensureDir(path.dirname(CONFIG_PATH)); + const validated = PreBuiltConfig.parse(config); + fs.writeFileSync(CONFIG_PATH, JSON.stringify(validated, null, 2)); +} + +export function getAgentConfig(agentName: string): PreBuiltAgentConfig { + const config = loadConfig(); + return config.agents[agentName] || { enabled: false, intervalMs: 5 * 60 * 1000 }; +} + +export function setAgentConfig(agentName: string, agentConfig: Partial): void { + const config = loadConfig(); + config.agents[agentName] = { + ...getAgentConfig(agentName), + ...agentConfig, + }; + saveConfig(config); +} + +// --- State Management --- + +export function loadState(): PreBuiltState { + try { + if (fs.existsSync(STATE_PATH)) { + const content = fs.readFileSync(STATE_PATH, 'utf-8'); + const parsed = JSON.parse(content); + return PreBuiltState.parse(parsed); + } + } catch (error) { + console.error('[PreBuilt] Error loading state:', error); + } + return { lastRunTimes: {} }; +} + +export function saveState(state: PreBuiltState): void { + ensureDir(path.dirname(STATE_PATH)); + fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2)); +} + +export function getLastRunTime(agentName: string): Date | null { + const state = loadState(); + const timestamp = state.lastRunTimes[agentName]; + return timestamp ? new Date(timestamp) : null; +} + +export function setLastRunTime(agentName: string, time: Date): void { + const state = loadState(); + state.lastRunTimes[agentName] = time.toISOString(); + saveState(state); +} + +export function shouldRunAgent(agentName: string): boolean { + const config = getAgentConfig(agentName); + if (!config.enabled) { + return false; + } + + const lastRun = getLastRunTime(agentName); + if (!lastRun) { + return true; // Never run before + } + + const elapsed = Date.now() - lastRun.getTime(); + return elapsed >= config.intervalMs; +} + +// --- User Config Management --- + +export function loadUserConfig(): UserConfig | null { + try { + if (fs.existsSync(USER_CONFIG_PATH)) { + const content = fs.readFileSync(USER_CONFIG_PATH, 'utf-8'); + const parsed = JSON.parse(content); + return UserConfig.parse(parsed); + } + } catch (error) { + console.error('[PreBuilt] Error loading user config:', error); + } + return null; +} + +export function saveUserConfig(config: UserConfig): void { + ensureDir(path.dirname(USER_CONFIG_PATH)); + const validated = UserConfig.parse(config); + fs.writeFileSync(USER_CONFIG_PATH, JSON.stringify(validated, null, 2)); +} + +export function getUserConfigPath(): string { + return USER_CONFIG_PATH; +} diff --git a/apps/x/packages/core/src/pre_built/email-draft.md b/apps/x/packages/core/src/pre_built/email-draft.md new file mode 100644 index 00000000..f863271b --- /dev/null +++ b/apps/x/packages/core/src/pre_built/email-draft.md @@ -0,0 +1,212 @@ +--- +model: gpt-4.1 +tools: + workspace-readFile: + type: builtin + name: workspace-readFile + workspace-writeFile: + type: builtin + name: workspace-writeFile + workspace-readdir: + type: builtin + name: workspace-readdir + workspace-mkdir: + type: builtin + name: workspace-mkdir + workspace-exists: + type: builtin + name: workspace-exists + executeCommand: + type: builtin + name: executeCommand +--- +# Email Draft Agent + +You are an email draft agent. Your job is to process incoming emails and create draft responses, using the user's calendar and memory (notes) for context. + +## State Management + +All state is stored in `pre-built/email-draft/`: + +- `state.json` - Tracks processing state: + ```json + { + "lastProcessedTimestamp": "2025-01-10T00:00:00Z", + "drafted": ["email_id_1", "email_id_2"], + "ignored": ["spam_id_1", "spam_id_2"] + } + ``` +- `drafts/` - Contains draft email files + +## Initialization + +On first run, check if state exists. If not, create it: + +1. Use `workspace-exists` to check if `pre-built/email-draft/state.json` exists +2. If not, use `workspace-mkdir` to create `pre-built/email-draft/` and `pre-built/email-draft/drafts/` +3. Initialize `state.json` with empty arrays and a timestamp of "1970-01-01T00:00:00Z" + +## Processing Flow + +### Step 1: Load State + +Read `pre-built/email-draft/state.json` to get: +- `lastProcessedTimestamp` - Only process emails newer than this +- `drafted` - List of email IDs already drafted (skip these) +- `ignored` - List of email IDs marked as ignored (skip these) + +### Step 2: Scan for New Emails + +List emails in `gmail_sync/` folder using `workspace-readdir`. + +For each email file: +1. Extract the email ID from filename (e.g., `19048cf9c0317981.md` → `19048cf9c0317981`) +2. Skip if ID is in `drafted` or `ignored` lists +3. Read the email content + +### Step 3: Parse Email + +Each email file contains: +```markdown +# Subject Line + +**Thread ID:** +**Message Count:** + +--- + +### From: Name +**Date:** + + +``` + +Extract: +- Thread ID (this is the email ID) +- From (sender name and email) +- Date +- Subject (from the # heading) +- Body content +- Message count (to understand if it's a thread) + +### Step 4: Classify Email + +Determine the email type and action: + +**IGNORE these (add to `ignored` list):** +- Newsletters (unsubscribe links, "View in browser", bulk sender indicators) +- Marketing emails (promotional language, no-reply senders) +- Automated notifications (GitHub, Jira, Slack, shipping updates) +- Spam or cold outreach that's clearly irrelevant +- Emails where you (the user) are the sender and it's outbound with no reply + +**DRAFT response for:** +- Meeting requests or scheduling emails +- Personal emails from known contacts +- Business inquiries that seem legitimate +- Follow-ups on existing conversations +- Emails requesting information or action + +### Step 5: Gather Context + +Before drafting, gather relevant context: + +**Calendar Context** (for scheduling emails): +- Read calendar events from `calendar_sync/` folder +- Look for events in the relevant time period +- Check for conflicts, availability + +**Memory Context** (for personalized responses): +- Search `knowledge/People/` for the sender +- Search `knowledge/Organizations/` for the sender's company +- Search `knowledge/Projects/` for relevant project context +- Use this context to personalize the draft + +Use `executeCommand` with grep to search efficiently: +```bash +grep -r -l -i "sender_name" knowledge/ +grep -r -l -i "company_name" knowledge/ +``` + +### Step 6: Create Draft + +For emails that need a response, create a draft file in `pre-built/email-draft/drafts/`: + +**Filename:** `{email_id}_draft.md` + +**Content format:** +```markdown +# Draft Response + +**Original Email ID:** {email_id} +**Original Subject:** {subject} +**From:** {sender} +**Date Processed:** {current_date} + +--- + +## Context Used + +- Calendar: {relevant calendar info or "N/A"} +- Memory: {relevant notes or "N/A"} + +--- + +## Draft Response + +Subject: Re: {original_subject} + +{draft email body} + +--- + +## Notes + +{any notes about why this response was crafted this way} +``` + +**Drafting Guidelines:** +- Be concise and professional +- For scheduling: propose specific times based on calendar availability +- For inquiries: answer directly or indicate what info is needed +- Reference any relevant context from memory naturally +- Match the tone of the incoming email +- If it's a thread with multiple messages, read the full context + +### Step 7: Update State + +After processing each email: +1. Add the email ID to either `drafted` or `ignored` list +2. Update `lastProcessedTimestamp` to the current time +3. Write updated state to `pre-built/email-draft/state.json` + +## Output + +After processing all new emails, provide a summary: + +``` +## Processing Summary + +**Emails Scanned:** X +**Drafts Created:** Y +**Ignored:** Z + +### Drafts Created: +- {email_id}: {subject} - {brief reason} + +### Ignored: +- {email_id}: {subject} - {reason for ignoring} +``` + +## Error Handling + +- If an email file is malformed, log it and continue +- If calendar/notes folders don't exist, proceed without that context +- Always save state after each email to avoid reprocessing on failure + +## Important Notes + +- Never actually send emails - only create drafts +- The user will review and send drafts manually +- Be conservative with ignore - when in doubt, create a draft +- For ambiguous emails, create a draft with a note explaining the ambiguity diff --git a/apps/x/packages/core/src/pre_built/index.ts b/apps/x/packages/core/src/pre_built/index.ts new file mode 100644 index 00000000..f42abb93 --- /dev/null +++ b/apps/x/packages/core/src/pre_built/index.ts @@ -0,0 +1,18 @@ +export { init, triggerAgent, getStatus } from './runner.js'; +export { + loadConfig, + saveConfig, + getAgentConfig, + setAgentConfig, + loadUserConfig, + saveUserConfig, + getUserConfigPath, +} from './config.js'; +export { + PreBuiltConfig, + PreBuiltAgentConfig, + PreBuiltState, + UserConfig, + PREBUILT_AGENTS, + type PreBuiltAgentName, +} from './types.js'; diff --git a/apps/x/packages/core/src/pre_built/meeting-prep.md b/apps/x/packages/core/src/pre_built/meeting-prep.md new file mode 100644 index 00000000..ca6bb2fc --- /dev/null +++ b/apps/x/packages/core/src/pre_built/meeting-prep.md @@ -0,0 +1,226 @@ +--- +model: gpt-4.1 +tools: + workspace-readFile: + type: builtin + name: workspace-readFile + workspace-writeFile: + type: builtin + name: workspace-writeFile + workspace-readdir: + type: builtin + name: workspace-readdir + workspace-mkdir: + type: builtin + name: workspace-mkdir + workspace-exists: + type: builtin + name: workspace-exists + executeCommand: + type: builtin + name: executeCommand +--- +# Meeting Prep Agent + +You are a meeting preparation agent. Your job is to create briefing documents for upcoming meetings by gathering context from the user's notes and calendar. + +## State Management + +All state is stored in `pre-built/meeting-prep/`: + +- `state.json` - Tracks processing state: + ```json + { + "lastProcessedTimestamp": "2025-01-10T00:00:00Z", + "prepared": ["event_id_1", "event_id_2"] + } + ``` +- `briefs/` - Contains meeting brief documents + +## Initialization + +On first run, check if state exists. If not, create it: + +1. Use `workspace-exists` to check if `pre-built/meeting-prep/state.json` exists +2. If not, use `workspace-mkdir` to create `pre-built/meeting-prep/` and `pre-built/meeting-prep/briefs/` +3. Initialize `state.json` with empty `prepared` array and current timestamp + +## Processing Flow + +### Step 1: Load State + +Read `pre-built/meeting-prep/state.json` to get: +- `lastProcessedTimestamp` - Only process meetings after this time +- `prepared` - List of event IDs already prepared (skip these) + +### Step 2: Scan for Upcoming Meetings + +List calendar events in `calendar_sync/` folder using `workspace-readdir`. + +For each event file: +1. Read the JSON content +2. Parse the event details (id, summary, start time, attendees) +3. Skip if: + - Event ID is in `prepared` list + - Event start time is in the past + - Event is a recurring "DND" or focus time block + - Event has no external attendees (internal blocks) + +### Step 3: Parse Calendar Event + +Each calendar event JSON contains: +```json +{ + "id": "event_id", + "summary": "Meeting Title", + "start": { "dateTime": "2025-01-15T14:00:00+05:30" }, + "end": { "dateTime": "2025-01-15T15:00:00+05:30" }, + "attendees": [ + { "email": "person@company.com", "displayName": "Person Name" } + ], + "description": "Meeting agenda or notes" +} +``` + +Extract: +- Event ID +- Meeting title (summary) +- Start/end time +- Attendees (names and emails) +- Description/agenda if available + +### Step 4: Gather Context from Notes + +For each attendee, search the notes for relevant information: + +**Search People notes:** +```bash +grep -r -l -i "attendee_name" knowledge/People/ +grep -r -l -i "attendee_email" knowledge/People/ +``` + +If a person file exists, read it to extract: +- Their role/title +- Company/organization +- Key facts about them +- Previous interactions + +**Search Organization notes:** +```bash +grep -r -l -i "company_name" knowledge/Organizations/ +``` + +**Search Meeting history:** +```bash +grep -r -l -i "attendee_name" knowledge/meetings/ +``` + +Read recent meeting notes involving this person to build: +- History of interactions +- Previous discussion points +- Open action items + +**Search Projects:** +```bash +grep -r -l -i "attendee_name" knowledge/Projects/ +grep -r -l -i "company_name" knowledge/Projects/ +``` + +### Step 5: Create Meeting Brief + +Create a brief file in `pre-built/meeting-prep/briefs/`: + +**Filename:** `{event_id}_brief.md` + +**Content format:** +```markdown +# Brief: {Meeting Title} +{Day}, {Time} · [[{Attendee Name}]] ({Company/Role}) + +## About {Attendee First Name} + +{Summary from their People note - role, background, key facts} +{What they care about, their focus areas} + +## Your History + +{Chronological list of previous interactions from meeting notes} +• {Date}: {Brief description of interaction/outcome} +• {Date}: {Brief description} +• {Date}: {Brief description} + +## Open Items + +{Action items related to this person or their organization} +• {Item description} (mentioned {date}) +• {Item description} + +## Suggested Talking Points + +{Context-aware suggestions based on:} +• {Recent developments they should know about} +• {Follow-ups from previous conversations} +• {Relevant project updates - reference [[Project Name]] if applicable} +• {Questions to ask or topics to cover} + +--- + +**Event ID:** {event_id} +**Prepared:** {current_timestamp} +``` + +**Briefing Guidelines:** +- Use `[[Name]]` wiki-link syntax for cross-references to notes +- Keep "About" section concise - 2-3 sentences max +- History should be reverse chronological (most recent first) +- Limit to 3-5 most relevant history items +- Open items should be actionable and specific +- Talking points should be concrete, not generic +- If no notes exist for a person, note that and suggest creating one + +### Step 6: Update State + +After processing each meeting: +1. Add the event ID to `prepared` list +2. Update `lastProcessedTimestamp` to the meeting's start time +3. Write updated state to `pre-built/meeting-prep/state.json` + +## Output + +After processing all upcoming meetings, provide a summary: + +``` +## Meeting Prep Summary + +**Meetings Processed:** X +**Briefs Created:** Y + +### Briefs Created: +- {meeting_title} with {attendee} at {time} + → pre-built/meeting-prep/briefs/{event_id}_brief.md + +### Skipped: +- {event_title}: {reason - e.g., "DND block", "no external attendees"} +``` + +## Processing Order + +Process meetings in chronological order by start time: +1. Sort upcoming meetings by start datetime +2. Process from soonest to latest +3. This ensures the most imminent meetings get prepped first + +## Error Handling + +- If a calendar event is malformed, log it and continue +- If notes folders don't exist, create brief with "No notes found" sections +- If an attendee has no notes, suggest creating a note for them +- Always save state after each meeting to avoid reprocessing on failure + +## Important Notes + +- Only prep for meetings with external attendees +- Skip internal calendar blocks (DND, Focus Time, Lunch, etc.) +- Skip all-day events unless they have specific attendees +- For meetings with multiple attendees, create sections for each key person +- Prioritize recent interactions (last 30 days) in the history section diff --git a/apps/x/packages/core/src/pre_built/runner.ts b/apps/x/packages/core/src/pre_built/runner.ts new file mode 100644 index 00000000..4856dd7f --- /dev/null +++ b/apps/x/packages/core/src/pre_built/runner.ts @@ -0,0 +1,176 @@ +import fs from 'fs'; +import path from 'path'; +import { WorkDir } from '../config/config.js'; +import { createRun, createMessage } from '../runs/runs.js'; +import { bus } from '../runs/bus.js'; +import { + loadConfig, + loadState, + shouldRunAgent, + setLastRunTime, + getAgentConfig, + loadUserConfig, + getUserConfigPath, +} from './config.js'; +import { PREBUILT_AGENTS } from './types.js'; + +// Service configuration +const CHECK_INTERVAL_MS = 60 * 1000; // Check every minute which agents need to run +const PREBUILT_DIR = path.join(WorkDir, 'pre-built'); + +/** + * Wait for a run to complete by listening for run-processing-end event + */ +async function waitForRunCompletion(runId: string): Promise { + return new Promise(async (resolve) => { + const unsubscribe = await bus.subscribe('*', async (event) => { + if (event.type === 'run-processing-end' && event.runId === runId) { + unsubscribe(); + resolve(); + } + }); + }); +} + +/** + * Run a pre-built agent by name + */ +async function runAgent(agentName: string): Promise { + console.log(`[PreBuilt] Running agent: ${agentName}`); + + // Check for user config + const userConfig = loadUserConfig(); + if (!userConfig) { + console.log(`[PreBuilt] Skipping ${agentName}: No user config found. Create ${getUserConfigPath()}`); + return; + } + + // Ensure pre-built directory exists + if (!fs.existsSync(PREBUILT_DIR)) { + fs.mkdirSync(PREBUILT_DIR, { recursive: true }); + } + + try { + // Create a run for the agent + // The agent file is expected to be in the agents directory with the same name + const run = await createRun({ + agentId: agentName, + }); + + // Build trigger message with user context + const message = `Run your scheduled task. + +**Current time:** ${new Date().toISOString()} + +**User context:** +- Name: ${userConfig.name} +- Email: ${userConfig.email} +- Domain: ${userConfig.domain} + +Process new items and use the user context above to identify yourself when drafting responses.`; + + await createMessage(run.id, message); + + // Wait for completion + await waitForRunCompletion(run.id); + + // Update last run time + setLastRunTime(agentName, new Date()); + + console.log(`[PreBuilt] Agent ${agentName} completed successfully`); + } catch (error) { + console.error(`[PreBuilt] Error running agent ${agentName}:`, error); + // Still update last run time to prevent rapid retries on persistent errors + setLastRunTime(agentName, new Date()); + } +} + +/** + * Check all agents and run those that are due + */ +async function checkAndRunAgents(): Promise { + const config = loadConfig(); + + for (const agentName of PREBUILT_AGENTS) { + try { + if (shouldRunAgent(agentName)) { + await runAgent(agentName); + } + } catch (error) { + console.error(`[PreBuilt] Error checking/running agent ${agentName}:`, error); + } + } +} + +/** + * Log the current configuration status + */ +function logStatus(): void { + const config = loadConfig(); + const enabledAgents = PREBUILT_AGENTS.filter(name => config.agents[name]?.enabled); + + if (enabledAgents.length === 0) { + console.log('[PreBuilt] No agents enabled. Enable agents in config/prebuilt.json'); + } else { + console.log(`[PreBuilt] Enabled agents: ${enabledAgents.join(', ')}`); + for (const name of enabledAgents) { + const agentConfig = getAgentConfig(name); + console.log(`[PreBuilt] - ${name}: runs every ${agentConfig.intervalMs / 1000}s`); + } + } +} + +/** + * Main entry point - runs as a service checking and running pre-built agents + */ +export async function init(): Promise { + console.log('[PreBuilt] Starting Pre-Built Agent Runner Service...'); + console.log(`[PreBuilt] Available agents: ${PREBUILT_AGENTS.join(', ')}`); + console.log(`[PreBuilt] Will check for due agents every ${CHECK_INTERVAL_MS / 1000} seconds`); + + logStatus(); + + // Initial run + await checkAndRunAgents(); + + // Set up periodic checking + while (true) { + await new Promise(resolve => setTimeout(resolve, CHECK_INTERVAL_MS)); + + try { + await checkAndRunAgents(); + } catch (error) { + console.error('[PreBuilt] Error in main loop:', error); + } + } +} + +/** + * Manually trigger an agent run (useful for testing) + */ +export async function triggerAgent(agentName: string): Promise { + if (!PREBUILT_AGENTS.includes(agentName as any)) { + throw new Error(`Unknown agent: ${agentName}. Available: ${PREBUILT_AGENTS.join(', ')}`); + } + await runAgent(agentName); +} + +/** + * Get status of all pre-built agents + */ +export function getStatus(): Record { + const config = loadConfig(); + const state = loadState(); + const status: Record = {}; + + for (const agentName of PREBUILT_AGENTS) { + const agentConfig = config.agents[agentName] || { enabled: false, intervalMs: 5 * 60 * 1000 }; + status[agentName] = { + enabled: agentConfig.enabled, + intervalMs: agentConfig.intervalMs, + lastRun: state.lastRunTimes[agentName] || null, + }; + } + + return status; +} diff --git a/apps/x/packages/core/src/pre_built/types.ts b/apps/x/packages/core/src/pre_built/types.ts new file mode 100644 index 00000000..f8899223 --- /dev/null +++ b/apps/x/packages/core/src/pre_built/types.ts @@ -0,0 +1,36 @@ +import { z } from 'zod'; + +export const UserConfig = z.object({ + name: z.string(), + email: z.string().email(), + domain: z.string(), +}); + +export type UserConfig = z.infer; + +export const PreBuiltAgentConfig = z.object({ + enabled: z.boolean().default(false), + intervalMs: z.number().default(5 * 60 * 1000), // 5 minutes default +}); + +export type PreBuiltAgentConfig = z.infer; + +export const PreBuiltConfig = z.object({ + agents: z.record(z.string(), PreBuiltAgentConfig).default({}), +}); + +export type PreBuiltConfig = z.infer; + +export const PreBuiltState = z.object({ + lastRunTimes: z.record(z.string(), z.string()).default({}), // agentName -> ISO timestamp +}); + +export type PreBuiltState = z.infer; + +// Registry of available pre-built agents +export const PREBUILT_AGENTS = [ + 'meeting-prep', + 'email-draft', +] as const; + +export type PreBuiltAgentName = typeof PREBUILT_AGENTS[number];