mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-06 19:35:44 +02:00
feat: Add Copilot trigger creation support
- Add support for One-Time and Recurring triggers in Copilot - Extend CopilotAssistantMessageActionPart schema with trigger config types - Update Copilot instructions with trigger creation examples and guidelines - Implement trigger action handling in messages.tsx component - Add trigger icons (⏰ for one-time, 🔄 for recurring) in action cards - Update workflow reducer to handle trigger creation via existing APIs - Fix action parser to recognize trigger config types in comment format - Add async trigger processing using createScheduledJobRule and createRecurringJobRule APIs Users can now ask Copilot to create triggers with natural language requests like: 'Create a daily report trigger at 9 AM' or 'Set up a one-time reminder for next Friday'
This commit is contained in:
parent
e988dfa87f
commit
24f930fa86
6 changed files with 215 additions and 9 deletions
|
|
@ -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}` };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ export function Action({
|
|||
{action.config_type === 'tool' && toolkitLogo ? (
|
||||
<PictureImg src={toolkitLogo} alt={"Toolkit logo"} className="h-5 w-5 object-contain" />
|
||||
) : (
|
||||
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' ? '🔄' : '💬'
|
||||
)}
|
||||
</span>
|
||||
<span className="font-semibold text-sm text-zinc-800 dark:text-zinc-100 truncate flex-1">
|
||||
|
|
@ -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' ? '🔄' : '💬'}
|
||||
</span>
|
||||
<span className="font-semibold text-sm text-zinc-800 dark:text-zinc-100 truncate flex-1">
|
||||
{action.action === 'create_new' ? 'Add' : action.action === 'edit' ? 'Edit' : 'Delete'} {action.config_type}: {action.name}
|
||||
|
|
@ -444,4 +444,4 @@ export function StreamingAction({
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ function enrich(response: string): z.infer<typeof CopilotResponsePart> {
|
|||
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<typeof CopilotResponsePart> {
|
|||
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<typeof CopilotResponsePart> {
|
|||
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({
|
|||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<typeof Message>[];
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
|
@ -144,6 +153,28 @@ export type Action = {
|
|||
pipeline: Partial<z.infer<typeof WorkflowPipeline>>;
|
||||
defaultModel?: string;
|
||||
fromCopilot?: boolean;
|
||||
} | {
|
||||
type: "add_one_time_trigger";
|
||||
trigger: {
|
||||
name: string;
|
||||
scheduledTime: string;
|
||||
input: {
|
||||
messages: z.infer<typeof Message>[];
|
||||
};
|
||||
};
|
||||
fromCopilot?: boolean;
|
||||
} | {
|
||||
type: "add_recurring_trigger";
|
||||
trigger: {
|
||||
name: string;
|
||||
cron: string;
|
||||
input: {
|
||||
messages: z.infer<typeof Message>[];
|
||||
};
|
||||
};
|
||||
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 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|||
|
||||
</agent_tools>
|
||||
|
||||
<about_triggers>
|
||||
|
||||
## 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"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</about_triggers>
|
||||
|
||||
<about_pipelines>
|
||||
|
||||
## Section: Creating and Managing Pipelines
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue