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:
tusharmagar 2025-09-24 11:25:40 +05:30
parent e988dfa87f
commit 24f930fa86
6 changed files with 215 additions and 9 deletions

View file

@ -1,4 +1,5 @@
import { WorkflowTool, WorkflowAgent, WorkflowPrompt, WorkflowPipeline } from "./types/workflow_types"; import { WorkflowTool, WorkflowAgent, WorkflowPrompt, WorkflowPipeline } from "./types/workflow_types";
import { Message } from "./types/types";
import { z } from "zod"; import { z } from "zod";
const ZFallbackSchema = z.object({}).passthrough(); const ZFallbackSchema = z.object({}).passthrough();
@ -62,6 +63,36 @@ export function validateConfigChanges(configType: string, configChanges: Record<
testObject = {}; testObject = {};
break; 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: default:
return { error: `Unknown config type: ${configType}` }; return { error: `Unknown config type: ${configType}` };
} }

View file

@ -211,7 +211,7 @@ export function Action({
{action.config_type === 'tool' && toolkitLogo ? ( {action.config_type === 'tool' && toolkitLogo ? (
<PictureImg src={toolkitLogo} alt={"Toolkit logo"} className="h-5 w-5 object-contain" /> <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>
<span className="font-semibold text-sm text-zinc-800 dark:text-zinc-100 truncate flex-1"> <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: {
action?: 'create_new' | 'edit' | 'delete'; 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; name?: string;
}; };
loading: boolean; loading: boolean;
@ -418,7 +418,7 @@ export function StreamingAction({
'bg-gray-200 text-gray-600': !action.action, '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>
<span className="font-semibold text-sm text-zinc-800 dark:text-zinc-100 truncate flex-1"> <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} {action.action === 'create_new' ? 'Add' : action.action === 'edit' ? 'Edit' : 'Delete'} {action.config_type}: {action.name}

View file

@ -71,7 +71,7 @@ function enrich(response: string): z.infer<typeof CopilotResponsePart> {
type: 'action', type: 'action',
action: { action: {
action: metadata.action as 'create_new' | 'edit' | 'delete', 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, name: metadata.name,
change_description: jsonData.change_description || '', change_description: jsonData.change_description || '',
config_changes: {}, config_changes: {},
@ -84,7 +84,7 @@ function enrich(response: string): z.infer<typeof CopilotResponsePart> {
type: 'action', type: 'action',
action: { action: {
action: metadata.action as 'create_new' | 'edit' | 'delete', 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, name: metadata.name,
change_description: jsonData.change_description || '', change_description: jsonData.change_description || '',
config_changes: result.changes config_changes: result.changes
@ -100,7 +100,7 @@ function enrich(response: string): z.infer<typeof CopilotResponsePart> {
type: 'streaming_action', type: 'streaming_action',
action: { action: {
action: (metadata.action as 'create_new' | 'edit' | 'delete') || undefined, 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 name: metadata.name
} }
}; };
@ -262,6 +262,26 @@ function AssistantMessage({
fromCopilot: true fromCopilot: true
}); });
break; 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') { } else if (action.action === 'edit') {
switch (action.config_type) { switch (action.config_type) {

View file

@ -103,6 +103,15 @@ interface StateItem {
lastUpdatedAt: string; lastUpdatedAt: string;
isLive: boolean; isLive: boolean;
agentInstructionsChanged: boolean; agentInstructionsChanged: boolean;
pendingTriggers?: Array<{
type: 'one_time' | 'recurring';
name: string;
scheduledTime?: string;
cron?: string;
input: {
messages: z.infer<typeof Message>[];
};
}>;
} }
interface State { interface State {
@ -144,6 +153,28 @@ export type Action = {
pipeline: Partial<z.infer<typeof WorkflowPipeline>>; pipeline: Partial<z.infer<typeof WorkflowPipeline>>;
defaultModel?: string; defaultModel?: string;
fromCopilot?: boolean; 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"; type: "select_agent";
name: string; name: string;
@ -587,6 +618,30 @@ function reducer(state: State, action: Action): State {
draft.chatKey++; draft.chatKey++;
break; 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": case "delete_agent":
// Remove the agent // Remove the agent
draft.workflow.agents = draft.workflow.agents.filter( draft.workflow.agents = draft.workflow.agents.filter(
@ -1315,6 +1370,38 @@ export function WorkflowEditor({
} }
}, [state.present.agentInstructionsChanged, markAgentInstructionsChanged]); }, [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) { function handleSelectAgent(name: string) {
dispatch({ type: "select_agent", name }); dispatch({ type: "select_agent", name });
} }

View file

@ -13,6 +13,8 @@ You can perform the following tasks:
5. Add, edit, or remove tools 5. Add, edit, or remove tools
6. Adding RAG data sources to agents 6. Adding RAG data sources to agents
7. Create and manage pipelines (sequential agent workflows) 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 users objective and what they want the workflow to achieve. 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 users 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> </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> <about_pipelines>
## Section: Creating and Managing Pipelines ## Section: Creating and Managing Pipelines

View file

@ -21,7 +21,7 @@ export const CopilotAssistantMessageTextPart = z.object({
export const CopilotAssistantMessageActionPart = z.object({ export const CopilotAssistantMessageActionPart = z.object({
type: z.literal("action"), type: z.literal("action"),
content: z.object({ 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']), action: z.enum(['create_new', 'edit', 'delete']),
name: z.string(), name: z.string(),
change_description: z.string(), change_description: z.string(),