Signed-off-by: OpenClaw Bot <ai-agent@topdoglabs.com>
This commit is contained in:
parent
59f85de572
commit
62b730dae1
92
app/api/voxyz/cap-gates/route.ts
Normal file
92
app/api/voxyz/cap-gates/route.ts
Normal file
@ -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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ import {
|
|||||||
runDailyMissionGeneration
|
runDailyMissionGeneration
|
||||||
} from '@/lib/services/daily-mission-agent';
|
} from '@/lib/services/daily-mission-agent';
|
||||||
import { getProposalsForDate, approveProposal, rejectProposal } from '@/lib/services/proposal.service';
|
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
|
// GET /api/voxyz/daily-missions - Get today's daily mission
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
@ -38,7 +38,7 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch full proposal details
|
// Fetch full proposal details
|
||||||
const supabase = await createClient();
|
const supabase = getServiceSupabase();
|
||||||
const proposalIds = [
|
const proposalIds = [
|
||||||
dailyMission.primary_mission_id,
|
dailyMission.primary_mission_id,
|
||||||
dailyMission.secondary_mission_id,
|
dailyMission.secondary_mission_id,
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
getTopProposalsForToday,
|
getTopProposalsForToday,
|
||||||
expireOldProposals
|
expireOldProposals
|
||||||
} from '@/lib/services/proposal.service';
|
} from '@/lib/services/proposal.service';
|
||||||
import { createClient } from '@/lib/supabase/server';
|
import { getServiceSupabase } from '@/lib/supabase/client';
|
||||||
|
|
||||||
// GET /api/voxyz/proposals - Get proposals
|
// GET /api/voxyz/proposals - Get proposals
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
@ -33,7 +33,7 @@ export async function GET(request: NextRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get all proposals with optional status filter
|
// Get all proposals with optional status filter
|
||||||
const supabase = await createClient();
|
const supabase = getServiceSupabase();
|
||||||
let query = supabase.from('ops_mission_proposals').select('*');
|
let query = supabase.from('ops_mission_proposals').select('*');
|
||||||
|
|
||||||
if (status) {
|
if (status) {
|
||||||
@ -72,7 +72,7 @@ export async function POST(request: NextRequest) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const supabase = await createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const proposal = {
|
const proposal = {
|
||||||
proposal_date: new Date().toISOString().split('T')[0],
|
proposal_date: new Date().toISOString().split('T')[0],
|
||||||
|
|||||||
150
app/api/voxyz/trigger-rules/route.ts
Normal file
150
app/api/voxyz/trigger-rules/route.ts
Normal file
@ -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<string, unknown> = {
|
||||||
|
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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ import {
|
|||||||
MissionProposal,
|
MissionProposal,
|
||||||
DailyMission
|
DailyMission
|
||||||
} from '@/lib/supabase/voxyz.types';
|
} from '@/lib/supabase/voxyz.types';
|
||||||
import { createClient } from '@/lib/supabase/client';
|
import { getServiceSupabase } from '@/lib/supabase/client';
|
||||||
|
|
||||||
const DEFAULT_CONFIG: CapGateConfig = {
|
const DEFAULT_CONFIG: CapGateConfig = {
|
||||||
dailyMissionLimit: 3,
|
dailyMissionLimit: 3,
|
||||||
@ -28,7 +28,7 @@ const DEFAULT_CONFIG: CapGateConfig = {
|
|||||||
* Get all active cap gates from the database
|
* Get all active cap gates from the database
|
||||||
*/
|
*/
|
||||||
export async function getActiveCapGates(): Promise<CapGate[]> {
|
export async function getActiveCapGates(): Promise<CapGate[]> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('ops_cap_gates')
|
.from('ops_cap_gates')
|
||||||
@ -48,7 +48,7 @@ export async function getActiveCapGates(): Promise<CapGate[]> {
|
|||||||
* Get a specific cap gate by type
|
* Get a specific cap gate by type
|
||||||
*/
|
*/
|
||||||
export async function getCapGateByType(gateType: CapGate['gate_type']): Promise<CapGate | null> {
|
export async function getCapGateByType(gateType: CapGate['gate_type']): Promise<CapGate | null> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('ops_cap_gates')
|
.from('ops_cap_gates')
|
||||||
@ -72,7 +72,7 @@ export async function updateCapGateValue(
|
|||||||
gateId: string,
|
gateId: string,
|
||||||
currentValue: number
|
currentValue: number
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('ops_cap_gates')
|
.from('ops_cap_gates')
|
||||||
@ -387,7 +387,7 @@ export async function getCapacityStatus(config: CapGateConfig = DEFAULT_CONFIG):
|
|||||||
focusTimeAllocatedMinutes: number;
|
focusTimeAllocatedMinutes: number;
|
||||||
reactionTimeReservedMinutes: number;
|
reactionTimeReservedMinutes: number;
|
||||||
}> {
|
}> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
// Get today's proposals
|
// Get today's proposals
|
||||||
@ -446,7 +446,7 @@ export async function canAddProposal(
|
|||||||
* Create default cap gates in database
|
* Create default cap gates in database
|
||||||
*/
|
*/
|
||||||
export async function initializeCapGates(config: CapGateConfig = DEFAULT_CONFIG): Promise<boolean> {
|
export async function initializeCapGates(config: CapGateConfig = DEFAULT_CONFIG): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
// Check if cap gates already exist
|
// Check if cap gates already exist
|
||||||
const { count } = await supabase
|
const { count } = await supabase
|
||||||
@ -557,7 +557,7 @@ export async function initializeCapGates(config: CapGateConfig = DEFAULT_CONFIG)
|
|||||||
* Reset cap gate values for a new day
|
* Reset cap gate values for a new day
|
||||||
*/
|
*/
|
||||||
export async function resetCapGatesForNewDay(): Promise<boolean> {
|
export async function resetCapGatesForNewDay(): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('ops_cap_gates')
|
.from('ops_cap_gates')
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import {
|
|||||||
initializeCapGates,
|
initializeCapGates,
|
||||||
resetCapGatesForNewDay
|
resetCapGatesForNewDay
|
||||||
} from '@/lib/services/cap-gates.service';
|
} from '@/lib/services/cap-gates.service';
|
||||||
import { createClient } from '@/lib/supabase/client';
|
import { getServiceSupabase } from '@/lib/supabase/client';
|
||||||
|
|
||||||
const GENERATION_HOUR = 7;
|
const GENERATION_HOUR = 7;
|
||||||
const GENERATION_MINUTE = 0;
|
const GENERATION_MINUTE = 0;
|
||||||
@ -279,7 +279,7 @@ async function canAddAnotherProposal(
|
|||||||
async function createCalendarBlockForProposal(
|
async function createCalendarBlockForProposal(
|
||||||
proposal: MissionProposal
|
proposal: MissionProposal
|
||||||
): Promise<CalendarBlock | null> {
|
): Promise<CalendarBlock | null> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const startTime = proposal.suggested_start_time
|
const startTime = proposal.suggested_start_time
|
||||||
? new Date(proposal.suggested_start_time)
|
? new Date(proposal.suggested_start_time)
|
||||||
@ -325,7 +325,7 @@ async function createDailyMissionRecord(
|
|||||||
proposals: MissionProposal[],
|
proposals: MissionProposal[],
|
||||||
calendarBlocks: CalendarBlock[]
|
calendarBlocks: CalendarBlock[]
|
||||||
): Promise<DailyMission> {
|
): Promise<DailyMission> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const totalMinutes = proposals.reduce((sum, p) =>
|
const totalMinutes = proposals.reduce((sum, p) =>
|
||||||
sum + (p.estimated_duration_minutes || 120), 0
|
sum + (p.estimated_duration_minutes || 120), 0
|
||||||
@ -396,11 +396,11 @@ async function createDailyMissionRecord(
|
|||||||
/**
|
/**
|
||||||
* Start execution log entry
|
* 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',
|
agentName: 'proposal_generator' | 'cap_gate_validator' | 'calendar_scheduler' | 'trigger_monitor' | 'mission_optimizer' | 'stale_task_detector',
|
||||||
executionType: 'scheduled' | 'triggered' | 'manual' | 'reaction'
|
executionType: 'scheduled' | 'triggered' | 'manual' | 'reaction'
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('ops_agent_execution_log')
|
.from('ops_agent_execution_log')
|
||||||
@ -429,7 +429,7 @@ async function startExecutionLog(
|
|||||||
/**
|
/**
|
||||||
* Complete execution log entry
|
* Complete execution log entry
|
||||||
*/
|
*/
|
||||||
async function completeExecutionLog(
|
export async function completeExecutionLog(
|
||||||
logId: string,
|
logId: string,
|
||||||
result: {
|
result: {
|
||||||
status: 'success' | 'partial' | 'failed';
|
status: 'success' | 'partial' | 'failed';
|
||||||
@ -441,7 +441,7 @@ async function completeExecutionLog(
|
|||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
}
|
}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
await supabase
|
await supabase
|
||||||
.from('ops_agent_execution_log')
|
.from('ops_agent_execution_log')
|
||||||
@ -470,7 +470,7 @@ function isNewDay(): boolean {
|
|||||||
* Get today's daily mission
|
* Get today's daily mission
|
||||||
*/
|
*/
|
||||||
export async function getTodaysDailyMission(): Promise<DailyMission | null> {
|
export async function getTodaysDailyMission(): Promise<DailyMission | null> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
@ -537,7 +537,7 @@ export async function runDailyMissionGeneration(): Promise<DailyMissionGeneratio
|
|||||||
export async function approveDailyMission(
|
export async function approveDailyMission(
|
||||||
missionId: string
|
missionId: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('ops_daily_missions')
|
.from('ops_daily_missions')
|
||||||
@ -560,7 +560,7 @@ export async function approveDailyMission(
|
|||||||
export async function scheduleDailyMission(
|
export async function scheduleDailyMission(
|
||||||
missionId: string
|
missionId: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
// Get mission details
|
// Get mission details
|
||||||
const { data: mission } = await supabase
|
const { data: mission } = await supabase
|
||||||
@ -621,7 +621,7 @@ export async function completeDailyMission(
|
|||||||
rating?: number,
|
rating?: number,
|
||||||
notes?: string
|
notes?: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('ops_daily_missions')
|
.from('ops_daily_missions')
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
ProposalStatus,
|
ProposalStatus,
|
||||||
ProposalServiceConfig
|
ProposalServiceConfig
|
||||||
} from '@/lib/supabase/voxyz.types';
|
} from '@/lib/supabase/voxyz.types';
|
||||||
import { createClient } from '@/lib/supabase/client';
|
import { getServiceSupabase } from '@/lib/supabase/client';
|
||||||
import { Task } from '@/lib/data/tasks';
|
import { Task } from '@/lib/data/tasks';
|
||||||
import { Project } from '@/lib/data/projects';
|
import { Project } from '@/lib/data/projects';
|
||||||
|
|
||||||
@ -136,9 +136,10 @@ function calculateProjectImportanceScore(project: Project | undefined): number {
|
|||||||
function calculateBlockedScore(task: Task, context: ProposalContext): number {
|
function calculateBlockedScore(task: Task, context: ProposalContext): number {
|
||||||
if (task.status === 'blocked') {
|
if (task.status === 'blocked') {
|
||||||
// Check how long it's been blocked
|
// Check how long it's been blocked
|
||||||
const blockedHours = task.comments?.reduce((hours, comment) => {
|
const blockedHours = task.comments?.reduce<number>((hours, comment) => {
|
||||||
if (comment.text?.toLowerCase().includes('blocked')) {
|
const item = comment as { text?: unknown; createdAt?: unknown };
|
||||||
const commentTime = new Date(comment.createdAt);
|
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);
|
const blockedHours = (context.currentTime.getTime() - commentTime.getTime()) / (1000 * 60 * 60);
|
||||||
return Math.max(hours, blockedHours);
|
return Math.max(hours, blockedHours);
|
||||||
}
|
}
|
||||||
@ -386,12 +387,20 @@ export function createProposalFromTask(
|
|||||||
source_task_ids: [task.id],
|
source_task_ids: [task.id],
|
||||||
source_project_ids: project ? [project.id] : [],
|
source_project_ids: project ? [project.id] : [],
|
||||||
title: task.title,
|
title: task.title,
|
||||||
description: task.description,
|
description: task.description ?? null,
|
||||||
rationale,
|
rationale,
|
||||||
ai_analysis: aiAnalysis,
|
ai_analysis: aiAnalysis,
|
||||||
suggested_start_time: suggestedStartTime?.toISOString() || null,
|
suggested_start_time: suggestedStartTime?.toISOString() || null,
|
||||||
suggested_end_time: suggestedEndTime?.toISOString() || null,
|
suggested_end_time: suggestedEndTime?.toISOString() || null,
|
||||||
estimated_duration_minutes: estimatedDuration,
|
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(),
|
expires_at: new Date(context.currentTime.getTime() + config.defaultProposalExpiryHours * 60 * 60 * 1000).toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -575,7 +584,7 @@ export async function saveProposal(
|
|||||||
proposal: MissionProposalInsert,
|
proposal: MissionProposalInsert,
|
||||||
capGateResult: CapGateCheckResult
|
capGateResult: CapGateCheckResult
|
||||||
): Promise<MissionProposal | null> {
|
): Promise<MissionProposal | null> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const proposalWithCapGates: MissionProposalInsert = {
|
const proposalWithCapGates: MissionProposalInsert = {
|
||||||
...proposal,
|
...proposal,
|
||||||
@ -601,7 +610,7 @@ export async function saveProposal(
|
|||||||
* Get proposals for a specific date
|
* Get proposals for a specific date
|
||||||
*/
|
*/
|
||||||
export async function getProposalsForDate(date: Date): Promise<MissionProposal[]> {
|
export async function getProposalsForDate(date: Date): Promise<MissionProposal[]> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
const dateString = date.toISOString().split('T')[0];
|
const dateString = date.toISOString().split('T')[0];
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
@ -625,7 +634,7 @@ export async function approveProposal(
|
|||||||
proposalId: string,
|
proposalId: string,
|
||||||
userId: string
|
userId: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('ops_mission_proposals')
|
.from('ops_mission_proposals')
|
||||||
@ -651,7 +660,7 @@ export async function rejectProposal(
|
|||||||
proposalId: string,
|
proposalId: string,
|
||||||
reason: string
|
reason: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('ops_mission_proposals')
|
.from('ops_mission_proposals')
|
||||||
@ -677,7 +686,7 @@ export async function completeProposal(
|
|||||||
proposalId: string,
|
proposalId: string,
|
||||||
notes?: string
|
notes?: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('ops_mission_proposals')
|
.from('ops_mission_proposals')
|
||||||
@ -718,7 +727,7 @@ export async function getTopProposalsForToday(limit: number = 3): Promise<Missio
|
|||||||
* Expire old proposals
|
* Expire old proposals
|
||||||
*/
|
*/
|
||||||
export async function expireOldProposals(): Promise<number> {
|
export async function expireOldProposals(): Promise<number> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
|
|||||||
@ -13,14 +13,14 @@ import {
|
|||||||
TriggerLog
|
TriggerLog
|
||||||
} from '@/lib/supabase/voxyz.types';
|
} from '@/lib/supabase/voxyz.types';
|
||||||
import { Task } from '@/lib/data/tasks';
|
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';
|
import { startExecutionLog, completeExecutionLog } from '@/lib/services/daily-mission-agent';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all active trigger rules
|
* Get all active trigger rules
|
||||||
*/
|
*/
|
||||||
export async function getActiveTriggerRules(): Promise<TriggerRule[]> {
|
export async function getActiveTriggerRules(): Promise<TriggerRule[]> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('ops_trigger_rules')
|
.from('ops_trigger_rules')
|
||||||
@ -224,7 +224,7 @@ function evaluateHighPriorityTrigger(
|
|||||||
const triggeredTasks = context.activeTasks.filter(task => {
|
const triggeredTasks = context.activeTasks.filter(task => {
|
||||||
if (task.priority !== priorityFilter) return false;
|
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;
|
return createdAt > oneHourAgo;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -419,7 +419,7 @@ async function isOnCooldown(rule: TriggerRule): Promise<boolean> {
|
|||||||
* Check if trigger has reached daily limit
|
* Check if trigger has reached daily limit
|
||||||
*/
|
*/
|
||||||
async function hasReachedDailyLimit(rule: TriggerRule): Promise<boolean> {
|
async function hasReachedDailyLimit(rule: TriggerRule): Promise<boolean> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
const { count, error } = await supabase
|
const { count, error } = await supabase
|
||||||
@ -440,7 +440,7 @@ async function hasReachedDailyLimit(rule: TriggerRule): Promise<boolean> {
|
|||||||
* Update trigger timestamp
|
* Update trigger timestamp
|
||||||
*/
|
*/
|
||||||
async function updateTriggerTimestamp(ruleId: string): Promise<void> {
|
async function updateTriggerTimestamp(ruleId: string): Promise<void> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
await supabase
|
await supabase
|
||||||
.from('ops_trigger_rules')
|
.from('ops_trigger_rules')
|
||||||
@ -459,7 +459,7 @@ async function logTrigger(
|
|||||||
result: TriggerEvaluationResult,
|
result: TriggerEvaluationResult,
|
||||||
success: boolean
|
success: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
await supabase
|
await supabase
|
||||||
.from('ops_trigger_log')
|
.from('ops_trigger_log')
|
||||||
@ -527,7 +527,7 @@ export async function triggerManualEvaluation(
|
|||||||
taskIds?: string[]
|
taskIds?: string[]
|
||||||
): Promise<TriggerEvaluationResult[]> {
|
): Promise<TriggerEvaluationResult[]> {
|
||||||
// Fetch active trigger rules of this type
|
// Fetch active trigger rules of this type
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { data: rules } = await supabase
|
const { data: rules } = await supabase
|
||||||
.from('ops_trigger_rules')
|
.from('ops_trigger_rules')
|
||||||
@ -570,7 +570,7 @@ export async function triggerManualEvaluation(
|
|||||||
* Get recent trigger logs
|
* Get recent trigger logs
|
||||||
*/
|
*/
|
||||||
export async function getRecentTriggerLogs(limit: number = 50): Promise<TriggerLog[]> {
|
export async function getRecentTriggerLogs(limit: number = 50): Promise<TriggerLog[]> {
|
||||||
const supabase = createClient();
|
const supabase = getServiceSupabase();
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('ops_trigger_log')
|
.from('ops_trigger_log')
|
||||||
|
|||||||
13
lib/services/voxyz/index.ts
Normal file
13
lib/services/voxyz/index.ts
Normal file
@ -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';
|
||||||
@ -151,6 +151,9 @@ export interface Database {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
type Json = any;
|
type Json = any;
|
||||||
|
|
||||||
|
// Voxyz Phase 10: Import Voxyz database extensions
|
||||||
|
export type { VoxyzDatabase } from './voxyz.types';
|
||||||
|
|
||||||
// Extended types for activity feed
|
// Extended types for activity feed
|
||||||
export interface TaskComment {
|
export interface TaskComment {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Database as BaseDatabase } from './database.types';
|
import { Database as BaseDatabase } from './database.types';
|
||||||
|
import type { Task } from '@/lib/data/tasks';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// ENUM TYPES
|
// ENUM TYPES
|
||||||
@ -184,7 +185,7 @@ export interface DailyMission {
|
|||||||
focus_time_start: string | null;
|
focus_time_start: string | null;
|
||||||
focus_time_end: string | null;
|
focus_time_end: string | null;
|
||||||
focus_time_minutes: number | null;
|
focus_time_minutes: number | null;
|
||||||
calendar_blocks: CalendarBlock[];
|
calendar_blocks: DailyMissionCalendarBlock[];
|
||||||
planned_reactions: number;
|
planned_reactions: number;
|
||||||
actual_reactions: number;
|
actual_reactions: number;
|
||||||
reaction_ratio: number;
|
reaction_ratio: number;
|
||||||
@ -194,6 +195,13 @@ export interface DailyMission {
|
|||||||
user_feedback: Record<string, unknown>;
|
user_feedback: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DailyMissionCalendarBlock {
|
||||||
|
id: string;
|
||||||
|
start_time: string;
|
||||||
|
end_time: string;
|
||||||
|
block_type: BlockType;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CalendarBlock {
|
export interface CalendarBlock {
|
||||||
id: string;
|
id: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@ -253,7 +261,7 @@ export interface AgentExecutionLog {
|
|||||||
|
|
||||||
export interface CapGateViolation {
|
export interface CapGateViolation {
|
||||||
gate_name: string;
|
gate_name: string;
|
||||||
gate_type: GateType;
|
gate_type: GateType | 'prioritization';
|
||||||
current_value: number;
|
current_value: number;
|
||||||
max_value: number;
|
max_value: number;
|
||||||
violation_message: string;
|
violation_message: string;
|
||||||
@ -342,7 +350,7 @@ export interface MissionProposalInput {
|
|||||||
|
|
||||||
export interface TriggerEvaluationContext {
|
export interface TriggerEvaluationContext {
|
||||||
currentTime: Date;
|
currentTime: Date;
|
||||||
activeTasks: BaseDatabase['public']['Tables']['tasks']['Row'][];
|
activeTasks: Task[];
|
||||||
recentProposals: MissionProposal[];
|
recentProposals: MissionProposal[];
|
||||||
todayMissions: DailyMission[];
|
todayMissions: DailyMission[];
|
||||||
userAvailability: CalendarBlock[];
|
userAvailability: CalendarBlock[];
|
||||||
|
|||||||
76
scripts/voxyz-daily-cron.sh
Executable file
76
scripts/voxyz-daily-cron.sh
Executable file
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user