diff --git a/apps/rowboat/app/lib/client_utils.ts b/apps/rowboat/app/lib/client_utils.ts index d47545b2..c74ac367 100644 --- a/apps/rowboat/app/lib/client_utils.ts +++ b/apps/rowboat/app/lib/client_utils.ts @@ -1,4 +1,5 @@ import { WorkflowTool, WorkflowAgent, WorkflowPrompt, WorkflowPipeline } from "./types/workflow_types"; +import { Message } from "./types/types"; import { z } from "zod"; const ZFallbackSchema = z.object({}).passthrough(); @@ -62,6 +63,36 @@ export function validateConfigChanges(configType: string, configChanges: Record< testObject = {}; break; } + case 'one_time_trigger': { + testObject = { + scheduledTime: new Date(0).toISOString(), + input: { + messages: [], + }, + }; + schema = z.object({ + scheduledTime: z.string().min(1), + input: z.object({ + messages: z.array(Message), + }), + }).passthrough(); + break; + } + case 'recurring_trigger': { + testObject = { + cron: '* * * * *', + input: { + messages: [], + }, + }; + schema = z.object({ + cron: z.string().min(1), + input: z.object({ + messages: z.array(Message), + }), + }).passthrough(); + break; + } default: return { error: `Unknown config type: ${configType}` }; } diff --git a/apps/rowboat/app/projects/[projectId]/copilot/components/actions.tsx b/apps/rowboat/app/projects/[projectId]/copilot/components/actions.tsx index 55f950ae..850c3b5e 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/components/actions.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/components/actions.tsx @@ -211,7 +211,7 @@ export function Action({ {action.config_type === 'tool' && toolkitLogo ? ( ) : ( - action.config_type === 'agent' ? 'πŸ§‘β€πŸ’Ό' : action.config_type === 'tool' ? 'πŸ› οΈ' : action.config_type === 'pipeline' ? 'βš™οΈ' : action.config_type === 'start_agent' ? '🏁' : action.config_type === 'prompt' ? 'πŸ’¬' : 'πŸ’¬' + action.config_type === 'agent' ? 'πŸ§‘β€πŸ’Ό' : action.config_type === 'tool' ? 'πŸ› οΈ' : action.config_type === 'pipeline' ? 'βš™οΈ' : action.config_type === 'start_agent' ? '🏁' : action.config_type === 'prompt' ? 'πŸ’¬' : action.config_type === 'one_time_trigger' ? '⏰' : action.config_type === 'recurring_trigger' ? 'πŸ”„' : 'πŸ’¬' )} @@ -379,7 +379,7 @@ export function StreamingAction({ }: { action: { action?: 'create_new' | 'edit' | 'delete'; - config_type?: 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent'; + config_type?: 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger'; name?: string; }; loading: boolean; @@ -418,7 +418,7 @@ export function StreamingAction({ 'bg-gray-200 text-gray-600': !action.action, } )}> - {action.config_type === 'agent' ? 'πŸ§‘β€πŸ’Ό' : action.config_type === 'tool' ? 'πŸ› οΈ' : action.config_type === 'pipeline' ? 'βš™οΈ' : action.config_type === 'start_agent' ? '🏁' : 'πŸ’¬'} + {action.config_type === 'agent' ? 'πŸ§‘β€πŸ’Ό' : action.config_type === 'tool' ? 'πŸ› οΈ' : action.config_type === 'pipeline' ? 'βš™οΈ' : action.config_type === 'start_agent' ? '🏁' : action.config_type === 'one_time_trigger' ? '⏰' : action.config_type === 'recurring_trigger' ? 'πŸ”„' : 'πŸ’¬'} {action.action === 'create_new' ? 'Add' : action.action === 'edit' ? 'Edit' : 'Delete'} {action.config_type}: {action.name} @@ -444,4 +444,4 @@ export function StreamingAction({ ); -} \ No newline at end of file +} diff --git a/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx b/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx index 6ca63979..c3cb2c1b 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx @@ -71,7 +71,7 @@ function enrich(response: string): z.infer { type: 'action', action: { action: metadata.action as 'create_new' | 'edit' | 'delete', - config_type: metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent', + config_type: metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger', name: metadata.name, change_description: jsonData.change_description || '', config_changes: {}, @@ -84,7 +84,7 @@ function enrich(response: string): z.infer { type: 'action', action: { action: metadata.action as 'create_new' | 'edit' | 'delete', - config_type: metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent', + config_type: metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger', name: metadata.name, change_description: jsonData.change_description || '', config_changes: result.changes @@ -100,7 +100,7 @@ function enrich(response: string): z.infer { type: 'streaming_action', action: { action: (metadata.action as 'create_new' | 'edit' | 'delete') || undefined, - config_type: (metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent') || undefined, + config_type: (metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger') || undefined, name: metadata.name } }; @@ -262,6 +262,26 @@ function AssistantMessage({ fromCopilot: true }); break; + case 'one_time_trigger': + dispatch({ + type: 'add_one_time_trigger', + trigger: { + name: action.name, + ...action.config_changes + }, + fromCopilot: true + }); + break; + case 'recurring_trigger': + dispatch({ + type: 'add_recurring_trigger', + trigger: { + name: action.name, + ...action.config_changes + }, + fromCopilot: true + }); + break; } } else if (action.action === 'edit') { switch (action.config_type) { @@ -603,4 +623,4 @@ export function Messages({
); -} \ No newline at end of file +} diff --git a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx index 476d8366..4f70fe98 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx @@ -103,6 +103,15 @@ interface StateItem { lastUpdatedAt: string; isLive: boolean; agentInstructionsChanged: boolean; + pendingTriggers?: Array<{ + type: 'one_time' | 'recurring'; + name: string; + scheduledTime?: string; + cron?: string; + input: { + messages: z.infer[]; + }; + }>; } interface State { @@ -144,6 +153,28 @@ export type Action = { pipeline: Partial>; defaultModel?: string; fromCopilot?: boolean; +} | { + type: "add_one_time_trigger"; + trigger: { + name: string; + scheduledTime: string; + input: { + messages: z.infer[]; + }; + }; + fromCopilot?: boolean; +} | { + type: "add_recurring_trigger"; + trigger: { + name: string; + cron: string; + input: { + messages: z.infer[]; + }; + }; + fromCopilot?: boolean; +} | { + type: "clear_pending_triggers"; } | { type: "select_agent"; name: string; @@ -587,6 +618,30 @@ function reducer(state: State, action: Action): State { draft.chatKey++; break; } + case "add_one_time_trigger": { + // Mark that we need to create a trigger - the actual API call will be handled outside the reducer + draft.pendingTriggers = draft.pendingTriggers || []; + draft.pendingTriggers.push({ + type: 'one_time', + ...action.trigger + }); + draft.pendingChanges = true; + break; + } + case "add_recurring_trigger": { + // Mark that we need to create a trigger - the actual API call will be handled outside the reducer + draft.pendingTriggers = draft.pendingTriggers || []; + draft.pendingTriggers.push({ + type: 'recurring', + ...action.trigger + }); + draft.pendingChanges = true; + break; + } + case "clear_pending_triggers": { + draft.pendingTriggers = []; + break; + } case "delete_agent": // Remove the agent draft.workflow.agents = draft.workflow.agents.filter( @@ -1315,6 +1370,38 @@ export function WorkflowEditor({ } }, [state.present.agentInstructionsChanged, markAgentInstructionsChanged]); + // Handle pending trigger creation + useEffect(() => { + if (state.present.pendingTriggers && state.present.pendingTriggers.length > 0) { + const processTriggers = async () => { + for (const trigger of state.present.pendingTriggers!) { + try { + if (trigger.type === 'one_time') { + const { createScheduledJobRule } = await import('@/app/actions/scheduled-job-rules.actions'); + await createScheduledJobRule({ + projectId, + input: trigger.input, + scheduledTime: trigger.scheduledTime! + }); + } else if (trigger.type === 'recurring') { + const { createRecurringJobRule } = await import('@/app/actions/recurring-job-rules.actions'); + await createRecurringJobRule({ + projectId, + input: trigger.input, + cron: trigger.cron! + }); + } + } catch (error) { + console.error(`Failed to create ${trigger.type} trigger:`, error); + } + } + // Clear pending triggers after processing + dispatch({ type: "clear_pending_triggers" }); + }; + processTriggers(); + } + }, [state.present.pendingTriggers, projectId]); + function handleSelectAgent(name: string) { dispatch({ type: "select_agent", name }); } diff --git a/apps/rowboat/src/application/lib/copilot/copilot_multi_agent_build.ts b/apps/rowboat/src/application/lib/copilot/copilot_multi_agent_build.ts index b5a7b215..4a078a42 100644 --- a/apps/rowboat/src/application/lib/copilot/copilot_multi_agent_build.ts +++ b/apps/rowboat/src/application/lib/copilot/copilot_multi_agent_build.ts @@ -13,6 +13,8 @@ You can perform the following tasks: 5. Add, edit, or remove tools 6. Adding RAG data sources to agents 7. Create and manage pipelines (sequential agent workflows) +8. Create One-Time Triggers (scheduled to run once at a specific time) +9. Create Recurring Triggers (scheduled to run repeatedly using cron expressions) Always aim to fully resolve the user's query before yielding. Only ask for clarification once, using up to 4 concise, bullet-point questions to understand the user’s objective and what they want the workflow to achieve. @@ -193,6 +195,72 @@ Note: The agents have access to a tool called 'Generate Image'. This won't show + + +## Section: Creating Triggers + +Triggers are automated mechanisms that activate your agents at specific times or intervals. You can create two types of triggers: + +### One-Time Triggers +- Execute once at a specific date and time +- Use config_type: "one_time_trigger" +- Require scheduledTime (ISO datetime string) in config_changes +- Require input.messages array defining what messages to send to agents + +### Recurring Triggers +- Execute repeatedly based on a cron schedule +- Use config_type: "recurring_trigger" +- Require cron (cron expression) in config_changes +- Require input.messages array defining what messages to send to agents + +### When to Create Triggers +- User asks for scheduled automation (daily reports, weekly summaries) +- User mentions specific times ("every morning at 9 AM", "next Friday at 2 PM") +- User wants periodic tasks (monitoring, maintenance, data syncing) + +### Common Cron Patterns +- "0 9 * * *" - Daily at 9:00 AM +- "0 8 * * 1" - Every Monday at 8:00 AM +- "*/15 * * * *" - Every 15 minutes +- "0 0 1 * *" - First day of month at midnight + +### Example Trigger Actions + +CRITICAL: When creating triggers, follow the EXACT format shown below with comments above the JSON: +- Put "action", "config_type", and "name" as comments (starting with //) ABOVE the JSON +- The JSON should contain "change_description" and "config_changes" +- Always use "action: create_new" for new triggers + +One-time trigger example (COPY THIS EXACT FORMAT): +// action: create_new +// config_type: one_time_trigger +// name: Weekly Report - Dec 15 +{ + "change_description": "Create a one-time trigger to generate weekly report on December 15th at 2 PM", + "config_changes": { + "scheduledTime": "2024-12-15T14:00:00Z", + "input": { + "messages": [{"role": "user", "content": "Generate the weekly performance report"}] + } + } +} + +Recurring trigger example (COPY THIS EXACT FORMAT): +// action: create_new +// config_type: recurring_trigger +// name: Daily Status Check +{ + "change_description": "Create a recurring trigger to check system status every morning at 9 AM", + "config_changes": { + "cron": "0 9 * * *", + "input": { + "messages": [{"role": "user", "content": "Check system status and alert if any issues found"}] + } + } +} + + + ## Section: Creating and Managing Pipelines diff --git a/apps/rowboat/src/entities/models/copilot.ts b/apps/rowboat/src/entities/models/copilot.ts index eeff9742..6317a787 100644 --- a/apps/rowboat/src/entities/models/copilot.ts +++ b/apps/rowboat/src/entities/models/copilot.ts @@ -21,7 +21,7 @@ export const CopilotAssistantMessageTextPart = z.object({ export const CopilotAssistantMessageActionPart = z.object({ type: z.literal("action"), content: z.object({ - config_type: z.enum(['tool', 'agent', 'prompt', 'pipeline', 'start_agent']), + config_type: z.enum(['tool', 'agent', 'prompt', 'pipeline', 'start_agent', 'one_time_trigger', 'recurring_trigger']), action: z.enum(['create_new', 'edit', 'delete']), name: z.string(), change_description: z.string(),