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 { 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}` };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 user’s 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 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>
|
</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
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue