diff --git a/app/api/voxyz/cap-gates/route.ts b/app/api/voxyz/cap-gates/route.ts new file mode 100644 index 0000000..8c977ca --- /dev/null +++ b/app/api/voxyz/cap-gates/route.ts @@ -0,0 +1,92 @@ +/** + * Voxyz API Routes - Cap Gates + * Handles cap gate management and capacity checks + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { + getActiveCapGates, + getCapacityStatus, + canAddProposal, + initializeCapGates, + checkCapGates +} from '@/lib/services/cap-gates.service'; + +// GET /api/voxyz/cap-gates - Get cap gates status +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const status = searchParams.get('status'); + const check = searchParams.get('check'); + + if (status === 'capacity') { + const capacity = await getCapacityStatus(); + return NextResponse.json({ capacity }); + } + + if (check === 'proposal') { + const estimatedMinutes = parseInt(searchParams.get('minutes') || '120'); + const canAdd = await canAddProposal(estimatedMinutes); + return NextResponse.json(canAdd); + } + + const gates = await getActiveCapGates(); + return NextResponse.json({ gates }); + + } catch (error) { + console.error('Error fetching cap gates:', error); + return NextResponse.json( + { error: 'Failed to fetch cap gates' }, + { status: 500 } + ); + } +} + +// POST /api/voxyz/cap-gates - Initialize cap gates +export async function POST(request: NextRequest) { + try { + const success = await initializeCapGates(); + + if (!success) { + return NextResponse.json( + { error: 'Failed to initialize cap gates' }, + { status: 500 } + ); + } + + return NextResponse.json({ success: true }, { status: 201 }); + + } catch (error) { + console.error('Error initializing cap gates:', error); + return NextResponse.json( + { error: 'Failed to initialize cap gates' }, + { status: 500 } + ); + } +} + +// POST /api/voxyz/cap-gates/check - Check a proposal against cap gates +export async function PATCH(request: NextRequest) { + try { + const body = await request.json(); + const { estimatedMinutes, existingProposals } = body; + + if (!estimatedMinutes) { + return NextResponse.json( + { error: 'Missing required field: estimatedMinutes' }, + { status: 400 } + ); + } + + const result = await checkCapGates(estimatedMinutes, existingProposals || []); + + return NextResponse.json(result); + + } catch (error) { + console.error('Error checking cap gates:', error); + return NextResponse.json( + { error: 'Failed to check cap gates' }, + { status: 500 } + ); + } +} diff --git a/app/api/voxyz/daily-missions/route.ts b/app/api/voxyz/daily-missions/route.ts index fc0092e..0c56234 100644 --- a/app/api/voxyz/daily-missions/route.ts +++ b/app/api/voxyz/daily-missions/route.ts @@ -13,7 +13,7 @@ import { runDailyMissionGeneration } from '@/lib/services/daily-mission-agent'; import { getProposalsForDate, approveProposal, rejectProposal } from '@/lib/services/proposal.service'; -import { createClient } from '@/lib/supabase/server'; +import { getServiceSupabase } from '@/lib/supabase/client'; // GET /api/voxyz/daily-missions - Get today's daily mission export async function GET(request: NextRequest) { @@ -38,7 +38,7 @@ export async function GET(request: NextRequest) { } // Fetch full proposal details - const supabase = await createClient(); + const supabase = getServiceSupabase(); const proposalIds = [ dailyMission.primary_mission_id, dailyMission.secondary_mission_id, diff --git a/app/api/voxyz/proposals/route.ts b/app/api/voxyz/proposals/route.ts index d203cb2..6e1e310 100644 --- a/app/api/voxyz/proposals/route.ts +++ b/app/api/voxyz/proposals/route.ts @@ -12,7 +12,7 @@ import { getTopProposalsForToday, expireOldProposals } from '@/lib/services/proposal.service'; -import { createClient } from '@/lib/supabase/server'; +import { getServiceSupabase } from '@/lib/supabase/client'; // GET /api/voxyz/proposals - Get proposals export async function GET(request: NextRequest) { @@ -33,7 +33,7 @@ export async function GET(request: NextRequest) { } // Get all proposals with optional status filter - const supabase = await createClient(); + const supabase = getServiceSupabase(); let query = supabase.from('ops_mission_proposals').select('*'); if (status) { @@ -72,7 +72,7 @@ export async function POST(request: NextRequest) { ); } - const supabase = await createClient(); + const supabase = getServiceSupabase(); const proposal = { proposal_date: new Date().toISOString().split('T')[0], diff --git a/app/api/voxyz/trigger-rules/route.ts b/app/api/voxyz/trigger-rules/route.ts new file mode 100644 index 0000000..ea6094b --- /dev/null +++ b/app/api/voxyz/trigger-rules/route.ts @@ -0,0 +1,150 @@ +/** + * Voxyz API Routes - Trigger Rules + * Handles trigger rule execution and management + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { + getActiveTriggerRules, + evaluateAllTriggers, + triggerManualEvaluation, + runTriggerMonitor, + getRecentTriggerLogs +} from '@/lib/services/trigger-rules.service'; +import { fetchAllTasks } from '@/lib/data/tasks'; +import { getServiceSupabase } from '@/lib/supabase/client'; + +// GET /api/voxyz/trigger-rules - Get trigger rules and logs +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const logs = searchParams.get('logs'); + const limit = parseInt(searchParams.get('limit') || '50'); + + if (logs) { + const triggerLogs = await getRecentTriggerLogs(limit); + return NextResponse.json({ logs: triggerLogs }); + } + + const rules = await getActiveTriggerRules(); + return NextResponse.json({ rules }); + + } catch (error) { + console.error('Error fetching trigger rules:', error); + return NextResponse.json( + { error: 'Failed to fetch trigger rules' }, + { status: 500 } + ); + } +} + +// POST /api/voxyz/trigger-rules - Run trigger evaluation or manual trigger +export async function POST(request: NextRequest) { + try { + const body = await request.json(); + const { action, triggerType, taskIds } = body; + + if (action === 'run-monitor') { + // Run the trigger monitor agent + await runTriggerMonitor(); + return NextResponse.json({ + success: true, + message: 'Trigger monitor executed' + }); + } + + if (action === 'manual-trigger') { + if (!triggerType) { + return NextResponse.json( + { error: 'Missing required field: triggerType' }, + { status: 400 } + ); + } + + const results = await triggerManualEvaluation(triggerType, taskIds); + + return NextResponse.json({ + success: true, + triggered: results.length, + results, + }); + } + + if (action === 'evaluate-all') { + // Evaluate all triggers against current context + const tasks = await fetchAllTasks(); + const context = { + currentTime: new Date(), + activeTasks: tasks.filter(t => t.status !== 'done' && t.status !== 'canceled'), + recentProposals: [], + todayMissions: [], + userAvailability: [], + }; + + const results = await evaluateAllTriggers(context); + + return NextResponse.json({ + success: true, + evaluated: results.length, + triggered: results.filter(r => r.triggered).length, + results, + }); + } + + return NextResponse.json( + { error: 'Unknown action' }, + { status: 400 } + ); + + } catch (error) { + console.error('Error running trigger evaluation:', error); + return NextResponse.json( + { error: 'Failed to run trigger evaluation' }, + { status: 500 } + ); + } +} + +// PATCH /api/voxyz/trigger-rules - Update a trigger rule +export async function PATCH(request: NextRequest) { + try { + const body = await request.json(); + const { id, isActive, priority } = body; + + if (!id) { + return NextResponse.json( + { error: 'Missing required field: id' }, + { status: 400 } + ); + } + + const supabase = getServiceSupabase(); + + const updates: Record = { + updated_at: new Date().toISOString(), + }; + + if (isActive !== undefined) updates.is_active = isActive; + if (priority !== undefined) updates.priority = priority; + + const { data, error } = await supabase + .from('ops_trigger_rules') + .update(updates) + .eq('id', id) + .select() + .single(); + + if (error) { + throw error; + } + + return NextResponse.json({ rule: data }); + + } catch (error) { + console.error('Error updating trigger rule:', error); + return NextResponse.json( + { error: 'Failed to update trigger rule' }, + { status: 500 } + ); + } +} diff --git a/lib/services/cap-gates.service.ts b/lib/services/cap-gates.service.ts index 4e088c3..7af9c45 100644 --- a/lib/services/cap-gates.service.ts +++ b/lib/services/cap-gates.service.ts @@ -13,7 +13,7 @@ import { MissionProposal, DailyMission } from '@/lib/supabase/voxyz.types'; -import { createClient } from '@/lib/supabase/client'; +import { getServiceSupabase } from '@/lib/supabase/client'; const DEFAULT_CONFIG: CapGateConfig = { dailyMissionLimit: 3, @@ -28,7 +28,7 @@ const DEFAULT_CONFIG: CapGateConfig = { * Get all active cap gates from the database */ export async function getActiveCapGates(): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { data, error } = await supabase .from('ops_cap_gates') @@ -48,7 +48,7 @@ export async function getActiveCapGates(): Promise { * Get a specific cap gate by type */ export async function getCapGateByType(gateType: CapGate['gate_type']): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { data, error } = await supabase .from('ops_cap_gates') @@ -72,7 +72,7 @@ export async function updateCapGateValue( gateId: string, currentValue: number ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { error } = await supabase .from('ops_cap_gates') @@ -387,7 +387,7 @@ export async function getCapacityStatus(config: CapGateConfig = DEFAULT_CONFIG): focusTimeAllocatedMinutes: number; reactionTimeReservedMinutes: number; }> { - const supabase = createClient(); + const supabase = getServiceSupabase(); const today = new Date().toISOString().split('T')[0]; // Get today's proposals @@ -446,7 +446,7 @@ export async function canAddProposal( * Create default cap gates in database */ export async function initializeCapGates(config: CapGateConfig = DEFAULT_CONFIG): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); // Check if cap gates already exist const { count } = await supabase @@ -557,7 +557,7 @@ export async function initializeCapGates(config: CapGateConfig = DEFAULT_CONFIG) * Reset cap gate values for a new day */ export async function resetCapGatesForNewDay(): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { error } = await supabase .from('ops_cap_gates') diff --git a/lib/services/daily-mission-agent.ts b/lib/services/daily-mission-agent.ts index 27fa0b1..ef2a557 100644 --- a/lib/services/daily-mission-agent.ts +++ b/lib/services/daily-mission-agent.ts @@ -28,7 +28,7 @@ import { initializeCapGates, resetCapGatesForNewDay } from '@/lib/services/cap-gates.service'; -import { createClient } from '@/lib/supabase/client'; +import { getServiceSupabase } from '@/lib/supabase/client'; const GENERATION_HOUR = 7; const GENERATION_MINUTE = 0; @@ -279,7 +279,7 @@ async function canAddAnotherProposal( async function createCalendarBlockForProposal( proposal: MissionProposal ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const startTime = proposal.suggested_start_time ? new Date(proposal.suggested_start_time) @@ -325,7 +325,7 @@ async function createDailyMissionRecord( proposals: MissionProposal[], calendarBlocks: CalendarBlock[] ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const totalMinutes = proposals.reduce((sum, p) => sum + (p.estimated_duration_minutes || 120), 0 @@ -396,11 +396,11 @@ async function createDailyMissionRecord( /** * Start execution log entry */ -async function startExecutionLog( +export async function startExecutionLog( agentName: 'proposal_generator' | 'cap_gate_validator' | 'calendar_scheduler' | 'trigger_monitor' | 'mission_optimizer' | 'stale_task_detector', executionType: 'scheduled' | 'triggered' | 'manual' | 'reaction' ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { data, error } = await supabase .from('ops_agent_execution_log') @@ -429,7 +429,7 @@ async function startExecutionLog( /** * Complete execution log entry */ -async function completeExecutionLog( +export async function completeExecutionLog( logId: string, result: { status: 'success' | 'partial' | 'failed'; @@ -441,7 +441,7 @@ async function completeExecutionLog( errorMessage?: string; } ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); await supabase .from('ops_agent_execution_log') @@ -470,7 +470,7 @@ function isNewDay(): boolean { * Get today's daily mission */ export async function getTodaysDailyMission(): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const today = new Date().toISOString().split('T')[0]; const { data, error } = await supabase @@ -537,7 +537,7 @@ export async function runDailyMissionGeneration(): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { error } = await supabase .from('ops_daily_missions') @@ -560,7 +560,7 @@ export async function approveDailyMission( export async function scheduleDailyMission( missionId: string ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); // Get mission details const { data: mission } = await supabase @@ -621,7 +621,7 @@ export async function completeDailyMission( rating?: number, notes?: string ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { error } = await supabase .from('ops_daily_missions') diff --git a/lib/services/proposal.service.ts b/lib/services/proposal.service.ts index aa25237..af22bb7 100644 --- a/lib/services/proposal.service.ts +++ b/lib/services/proposal.service.ts @@ -13,7 +13,7 @@ import { ProposalStatus, ProposalServiceConfig } from '@/lib/supabase/voxyz.types'; -import { createClient } from '@/lib/supabase/client'; +import { getServiceSupabase } from '@/lib/supabase/client'; import { Task } from '@/lib/data/tasks'; import { Project } from '@/lib/data/projects'; @@ -136,9 +136,10 @@ function calculateProjectImportanceScore(project: Project | undefined): number { function calculateBlockedScore(task: Task, context: ProposalContext): number { if (task.status === 'blocked') { // Check how long it's been blocked - const blockedHours = task.comments?.reduce((hours, comment) => { - if (comment.text?.toLowerCase().includes('blocked')) { - const commentTime = new Date(comment.createdAt); + const blockedHours = task.comments?.reduce((hours, comment) => { + const item = comment as { text?: unknown; createdAt?: unknown }; + if (typeof item.text === 'string' && item.text.toLowerCase().includes('blocked') && typeof item.createdAt === 'string') { + const commentTime = new Date(item.createdAt); const blockedHours = (context.currentTime.getTime() - commentTime.getTime()) / (1000 * 60 * 60); return Math.max(hours, blockedHours); } @@ -386,12 +387,20 @@ export function createProposalFromTask( source_task_ids: [task.id], source_project_ids: project ? [project.id] : [], title: task.title, - description: task.description, + description: task.description ?? null, rationale, ai_analysis: aiAnalysis, suggested_start_time: suggestedStartTime?.toISOString() || null, suggested_end_time: suggestedEndTime?.toISOString() || null, estimated_duration_minutes: estimatedDuration, + approved_at: null, + approved_by: null, + rejected_at: null, + rejection_reason: null, + calendar_event_id: null, + calendar_synced_at: null, + executed_at: null, + execution_notes: null, expires_at: new Date(context.currentTime.getTime() + config.defaultProposalExpiryHours * 60 * 60 * 1000).toISOString(), }; } @@ -575,7 +584,7 @@ export async function saveProposal( proposal: MissionProposalInsert, capGateResult: CapGateCheckResult ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const proposalWithCapGates: MissionProposalInsert = { ...proposal, @@ -601,7 +610,7 @@ export async function saveProposal( * Get proposals for a specific date */ export async function getProposalsForDate(date: Date): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const dateString = date.toISOString().split('T')[0]; const { data, error } = await supabase @@ -625,7 +634,7 @@ export async function approveProposal( proposalId: string, userId: string ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { error } = await supabase .from('ops_mission_proposals') @@ -651,7 +660,7 @@ export async function rejectProposal( proposalId: string, reason: string ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { error } = await supabase .from('ops_mission_proposals') @@ -677,7 +686,7 @@ export async function completeProposal( proposalId: string, notes?: string ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { error } = await supabase .from('ops_mission_proposals') @@ -718,7 +727,7 @@ export async function getTopProposalsForToday(limit: number = 3): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const now = new Date().toISOString(); const { data, error } = await supabase diff --git a/lib/services/trigger-rules.service.ts b/lib/services/trigger-rules.service.ts index b60d8d1..1457798 100644 --- a/lib/services/trigger-rules.service.ts +++ b/lib/services/trigger-rules.service.ts @@ -13,14 +13,14 @@ import { TriggerLog } from '@/lib/supabase/voxyz.types'; import { Task } from '@/lib/data/tasks'; -import { createClient } from '@/lib/supabase/client'; +import { getServiceSupabase } from '@/lib/supabase/client'; import { startExecutionLog, completeExecutionLog } from '@/lib/services/daily-mission-agent'; /** * Get all active trigger rules */ export async function getActiveTriggerRules(): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { data, error } = await supabase .from('ops_trigger_rules') @@ -224,7 +224,7 @@ function evaluateHighPriorityTrigger( const triggeredTasks = context.activeTasks.filter(task => { if (task.priority !== priorityFilter) return false; - const createdAt = new Date(task.created_at || task.updatedAt); + const createdAt = new Date(task.createdAt || task.updatedAt); return createdAt > oneHourAgo; }); @@ -419,7 +419,7 @@ async function isOnCooldown(rule: TriggerRule): Promise { * Check if trigger has reached daily limit */ async function hasReachedDailyLimit(rule: TriggerRule): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const today = new Date().toISOString().split('T')[0]; const { count, error } = await supabase @@ -440,7 +440,7 @@ async function hasReachedDailyLimit(rule: TriggerRule): Promise { * Update trigger timestamp */ async function updateTriggerTimestamp(ruleId: string): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); await supabase .from('ops_trigger_rules') @@ -459,7 +459,7 @@ async function logTrigger( result: TriggerEvaluationResult, success: boolean ): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); await supabase .from('ops_trigger_log') @@ -527,7 +527,7 @@ export async function triggerManualEvaluation( taskIds?: string[] ): Promise { // Fetch active trigger rules of this type - const supabase = createClient(); + const supabase = getServiceSupabase(); const { data: rules } = await supabase .from('ops_trigger_rules') @@ -570,7 +570,7 @@ export async function triggerManualEvaluation( * Get recent trigger logs */ export async function getRecentTriggerLogs(limit: number = 50): Promise { - const supabase = createClient(); + const supabase = getServiceSupabase(); const { data, error } = await supabase .from('ops_trigger_log') diff --git a/lib/services/voxyz/index.ts b/lib/services/voxyz/index.ts new file mode 100644 index 0000000..9398620 --- /dev/null +++ b/lib/services/voxyz/index.ts @@ -0,0 +1,13 @@ +/** + * Voxyz Autonomous Architecture - Services Index + * Phase 10: Mission Control Autonomous System + */ + +// Database types +export * from '@/lib/supabase/voxyz.types'; + +// Services +export * from '../proposal.service'; +export * from '../cap-gates.service'; +export * from '../daily-mission-agent'; +export * from '../trigger-rules.service'; diff --git a/lib/supabase/database.types.ts b/lib/supabase/database.types.ts index 3a022a9..3bde5df 100644 --- a/lib/supabase/database.types.ts +++ b/lib/supabase/database.types.ts @@ -151,6 +151,9 @@ export interface Database { // eslint-disable-next-line @typescript-eslint/no-explicit-any type Json = any; +// Voxyz Phase 10: Import Voxyz database extensions +export type { VoxyzDatabase } from './voxyz.types'; + // Extended types for activity feed export interface TaskComment { id: string; diff --git a/lib/supabase/voxyz.types.ts b/lib/supabase/voxyz.types.ts index 74fec4b..879ac58 100644 --- a/lib/supabase/voxyz.types.ts +++ b/lib/supabase/voxyz.types.ts @@ -4,6 +4,7 @@ */ import { Database as BaseDatabase } from './database.types'; +import type { Task } from '@/lib/data/tasks'; // ============================================ // ENUM TYPES @@ -184,7 +185,7 @@ export interface DailyMission { focus_time_start: string | null; focus_time_end: string | null; focus_time_minutes: number | null; - calendar_blocks: CalendarBlock[]; + calendar_blocks: DailyMissionCalendarBlock[]; planned_reactions: number; actual_reactions: number; reaction_ratio: number; @@ -194,6 +195,13 @@ export interface DailyMission { user_feedback: Record; } +export interface DailyMissionCalendarBlock { + id: string; + start_time: string; + end_time: string; + block_type: BlockType; +} + export interface CalendarBlock { id: string; created_at: string; @@ -253,7 +261,7 @@ export interface AgentExecutionLog { export interface CapGateViolation { gate_name: string; - gate_type: GateType; + gate_type: GateType | 'prioritization'; current_value: number; max_value: number; violation_message: string; @@ -342,7 +350,7 @@ export interface MissionProposalInput { export interface TriggerEvaluationContext { currentTime: Date; - activeTasks: BaseDatabase['public']['Tables']['tasks']['Row'][]; + activeTasks: Task[]; recentProposals: MissionProposal[]; todayMissions: DailyMission[]; userAvailability: CalendarBlock[]; diff --git a/scripts/voxyz-daily-cron.sh b/scripts/voxyz-daily-cron.sh new file mode 100755 index 0000000..052d21a --- /dev/null +++ b/scripts/voxyz-daily-cron.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# Voxyz Daily Mission Generation Cron Job +# Runs at 7:00 AM daily to generate mission proposals +# Phase 10: Autonomous Architecture + +set -e + +# Configuration +API_BASE_URL="${VOXYZ_API_URL:-http://localhost:3001/api/voxyz}" +API_KEY="${VOXYZ_API_KEY:-}" +LOG_FILE="${VOXYZ_LOG_FILE:-/tmp/voxyz-daily-missions.log}" + +# Timestamp +TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') + +echo "[$TIMESTAMP] Starting Voxyz daily mission generation..." >> "$LOG_FILE" + +# Function to make API calls +make_api_call() { + local endpoint=$1 + local method=$2 + local data=$3 + + if [ -n "$API_KEY" ]; then + curl -s -X "$method" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $API_KEY" \ + -d "$data" \ + "${API_BASE_URL}${endpoint}" + else + curl -s -X "$method" \ + -H "Content-Type: application/json" \ + -d "$data" \ + "${API_BASE_URL}${endpoint}" + fi +} + +# Step 1: Check if missions already generated for today +echo "[$TIMESTAMP] Checking for existing daily missions..." >> "$LOG_FILE" +EXISTING=$(make_api_call "/daily-missions" "GET" "") + +if echo "$EXISTING" | grep -q '"dailyMission":null'; then + echo "[$TIMESTAMP] No existing daily mission found. Generating..." >> "$LOG_FILE" + + # Step 2: Generate daily missions + RESULT=$(make_api_call "/daily-missions" "POST" '{"targetMissionCount": 3, "includeReactions": true}') + + if echo "$RESULT" | grep -q '"dailyMission"'; then + echo "[$TIMESTAMP] Daily missions generated successfully" >> "$LOG_FILE" + + # Extract proposal count + PROPOSAL_COUNT=$(echo "$RESULT" | grep -o '"proposals":\[[^]]*\]' | tr ',' '\n' | wc -l) + echo "[$TIMESTAMP] Generated $PROPOSAL_COUNT proposals" >> "$LOG_FILE" + + # Step 3: Run trigger monitor to check for urgent items + echo "[$TIMESTAMP] Running trigger monitor..." >> "$LOG_FILE" + TRIGGER_RESULT=$(make_api_call "/trigger-rules" "POST" '{"action": "run-monitor"}') + echo "[$TIMESTAMP] Trigger monitor completed" >> "$LOG_FILE" + + # Step 4: Clean up expired proposals + echo "[$TIMESTAMP] Cleaning up expired proposals..." >> "$LOG_FILE" + EXPIRED_RESULT=$(make_api_call "/proposals" "DELETE" "") + EXPIRED_COUNT=$(echo "$EXPIRED_RESULT" | grep -o '"expiredCount":[0-9]*' | cut -d: -f2) + echo "[$TIMESTAMP] Expired $EXPIRED_COUNT old proposals" >> "$LOG_FILE" + + echo "[$TIMESTAMP] Daily mission generation completed successfully" >> "$LOG_FILE" + exit 0 + else + echo "[$TIMESTAMP] ERROR: Failed to generate daily missions" >> "$LOG_FILE" + echo "[$TIMESTAMP] Response: $RESULT" >> "$LOG_FILE" + exit 1 + fi +else + echo "[$TIMESTAMP] Daily missions already exist for today. Skipping generation." >> "$LOG_FILE" + exit 0 +fi