From da6fa8597fec718c102214fd95984a0dba774313 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Mon, 29 Sep 2025 10:48:07 +0530 Subject: [PATCH] added conext of triggers to the copilot along with being able to edit and delete triggers --- apps/rowboat/app/actions/composio.actions.ts | 4 +- apps/rowboat/app/actions/copilot.actions.ts | 5 +- .../app/projects/[projectId]/copilot/app.tsx | 17 +- .../copilot/components/actions.tsx | 6 +- .../copilot/components/messages.tsx | 281 ++++++++++++++---- .../[projectId]/copilot/use-copilot.tsx | 8 +- .../app/projects/[projectId]/workflow/app.tsx | 61 +++- .../projects/[projectId]/workflow/page.tsx | 44 ++- .../[projectId]/workflow/trigger-transform.ts | 77 +++++ .../[projectId]/workflow/workflow_editor.tsx | 7 + .../src/application/lib/copilot/copilot.ts | 64 +++- .../lib/copilot/copilot_multi_agent_build.ts | 33 ++ .../create-copilot-cached-turn.use-case.ts | 3 +- .../run-copilot-cached-turn.use-case.ts | 1 + apps/rowboat/src/entities/models/copilot.ts | 43 ++- .../create-copilot-cached-turn.controller.ts | 3 +- 16 files changed, 571 insertions(+), 86 deletions(-) create mode 100644 apps/rowboat/app/projects/[projectId]/workflow/trigger-transform.ts diff --git a/apps/rowboat/app/actions/composio.actions.ts b/apps/rowboat/app/actions/composio.actions.ts index daddc930..8af20aaa 100644 --- a/apps/rowboat/app/actions/composio.actions.ts +++ b/apps/rowboat/app/actions/composio.actions.ts @@ -157,6 +157,7 @@ export async function createComposioTriggerDeployment(request: { export async function listComposioTriggerDeployments(request: { projectId: string, cursor?: string, + limit?: number, }) { const user = await authCheck(); @@ -166,6 +167,7 @@ export async function listComposioTriggerDeployments(request: { userId: user.id, projectId: request.projectId, cursor: request.cursor, + limit: request.limit, }); } @@ -191,4 +193,4 @@ export async function fetchComposioTriggerDeployment(request: { deploymentId: st userId: user.id, deploymentId: request.deploymentId, }); -} \ No newline at end of file +} diff --git a/apps/rowboat/app/actions/copilot.actions.ts b/apps/rowboat/app/actions/copilot.actions.ts index de9b1bae..e4d86731 100644 --- a/apps/rowboat/app/actions/copilot.actions.ts +++ b/apps/rowboat/app/actions/copilot.actions.ts @@ -3,6 +3,7 @@ import { CopilotAPIRequest, CopilotChatContext, CopilotMessage, DataSourceSchemaForCopilot, + TriggerSchemaForCopilot, } from "../../src/entities/models/copilot"; import { Workflow} from "../lib/types/workflow_types"; @@ -26,7 +27,8 @@ export async function getCopilotResponseStream( messages: z.infer[], current_workflow_config: z.infer, context: z.infer | null, - dataSources?: z.infer[] + dataSources?: z.infer[], + triggers?: z.infer[] ): Promise<{ streamId: string; } | { billingError: string }> { @@ -42,6 +44,7 @@ export async function getCopilotResponseStream( workflow: current_workflow_config, context, dataSources, + triggers, } }); return { diff --git a/apps/rowboat/app/projects/[projectId]/copilot/app.tsx b/apps/rowboat/app/projects/[projectId]/copilot/app.tsx index 6af50863..f8d4eda3 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/app.tsx @@ -2,7 +2,7 @@ import { Button } from "@/components/ui/button"; import { Dropdown, DropdownItem, DropdownMenu, DropdownSection, DropdownTrigger, Spinner, Tooltip } from "@heroui/react"; import { useRef, useState, createContext, useContext, useCallback, forwardRef, useImperativeHandle, useEffect, Ref } from "react"; -import { CopilotChatContext } from "../../../../src/entities/models/copilot"; +import { CopilotChatContext, TriggerSchemaForCopilot } from "../../../../src/entities/models/copilot"; import { CopilotMessage } from "../../../../src/entities/models/copilot"; import { Workflow } from "@/app/lib/types/workflow_types"; import { DataSource } from "@/src/entities/models/data-source"; @@ -36,6 +36,8 @@ interface AppProps { onMessagesChange?: (messages: z.infer[]) => void; isInitialState?: boolean; dataSources?: z.infer[]; + triggers?: z.infer[]; + onTriggersUpdated?: () => Promise | void; } const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message: string) => void }, AppProps>(function App({ @@ -47,6 +49,8 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message onMessagesChange, isInitialState = false, dataSources, + triggers, + onTriggersUpdated, }, ref) { @@ -85,7 +89,8 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message projectId, workflow: workflowRef.current, context: effectiveContext, - dataSources: dataSources + dataSources: dataSources, + triggers: triggers }); // Store latest start/cancel functions in refs @@ -264,6 +269,8 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message onStatusBarChange={handleStatusBarChange} toolCalling={toolCalling} toolQuery={toolQuery} + triggers={triggers} + onTriggersUpdated={onTriggersUpdated} />
@@ -319,8 +326,10 @@ export const Copilot = forwardRef<{ handleUserMessage: (message: string) => void dispatch: (action: WorkflowDispatch) => void; isInitialState?: boolean; dataSources?: z.infer[]; + triggers?: z.infer[]; activePanel?: 'playground' | 'copilot'; onTogglePanel?: () => void; + onTriggersUpdated?: () => Promise | void; }>(({ projectId, workflow, @@ -328,8 +337,10 @@ export const Copilot = forwardRef<{ handleUserMessage: (message: string) => void dispatch, isInitialState = false, dataSources, + triggers, activePanel, onTogglePanel, + onTriggersUpdated, }, ref) => { console.log('🎪 Copilot wrapper component mounted:', { projectId, @@ -415,6 +426,8 @@ export const Copilot = forwardRef<{ handleUserMessage: (message: string) => void onMessagesChange={setMessages} isInitialState={isInitialState} dataSources={dataSources} + triggers={triggers} + onTriggersUpdated={onTriggersUpdated} />
diff --git a/apps/rowboat/app/projects/[projectId]/copilot/components/actions.tsx b/apps/rowboat/app/projects/[projectId]/copilot/components/actions.tsx index 850c3b5e..39483117 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 === 'one_time_trigger' ? '⏰' : action.config_type === 'recurring_trigger' ? '🔄' : '💬' + 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' ? '🔄' : action.config_type === 'external_trigger' ? '🔗' : '💬' )} @@ -379,7 +379,7 @@ export function StreamingAction({ }: { action: { action?: 'create_new' | 'edit' | 'delete'; - config_type?: 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger'; + config_type?: 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger' | 'external_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 === 'one_time_trigger' ? '⏰' : action.config_type === 'recurring_trigger' ? '🔄' : '💬'} + {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.config_type === 'external_trigger' ? '🔗' : '💬'} {action.action === 'create_new' ? 'Add' : action.action === 'edit' ? 'Edit' : 'Delete'} {action.config_type}: {action.name} diff --git a/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx b/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx index 70700225..b7f8b0c4 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx @@ -5,7 +5,7 @@ import { z } from "zod"; import { Workflow} from "@/app/lib/types/workflow_types"; import MarkdownContent from "@/app/lib/components/markdown-content"; import { MessageSquareIcon, EllipsisIcon, XIcon, CheckCheckIcon, ChevronDown, ChevronUp } from "lucide-react"; -import { CopilotMessage, CopilotAssistantMessage, CopilotAssistantMessageActionPart } from "@/src/entities/models/copilot"; +import { CopilotMessage, CopilotAssistantMessage, CopilotAssistantMessageActionPart, TriggerSchemaForCopilot } from "@/src/entities/models/copilot"; import { Action, StreamingAction } from './actions'; import { useParsedBlocks } from "../use-parsed-blocks"; import { validateConfigChanges } from "@/app/lib/client_utils"; @@ -13,9 +13,13 @@ import { PreviewModalProvider } from '../../workflow/preview-modal'; type ScheduledJobActionsModule = typeof import('@/app/actions/scheduled-job-rules.actions'); type RecurringJobActionsModule = typeof import('@/app/actions/recurring-job-rules.actions'); +type ComposioActionsModule = typeof import('@/app/actions/composio.actions'); + +type CopilotTriggerType = z.infer; let scheduledJobActionsPromise: Promise | null = null; let recurringJobActionsPromise: Promise | null = null; +let composioActionsPromise: Promise | null = null; function loadScheduledJobActions(): Promise { if (!scheduledJobActionsPromise) { @@ -31,6 +35,13 @@ function loadRecurringJobActions(): Promise { return recurringJobActionsPromise; } +function loadComposioActions(): Promise { + if (!composioActionsPromise) { + composioActionsPromise = import('@/app/actions/composio.actions'); + } + return composioActionsPromise; +} + const CopilotResponsePart = z.union([ z.object({ type: z.literal('text'), @@ -91,7 +102,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' | 'one_time_trigger' | 'recurring_trigger', + config_type: metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger' | 'external_trigger', name: metadata.name, change_description: jsonData.change_description || '', config_changes: {}, @@ -104,7 +115,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' | 'one_time_trigger' | 'recurring_trigger', + config_type: metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger' | 'external_trigger', name: metadata.name, change_description: jsonData.change_description || '', config_changes: result.changes @@ -120,7 +131,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' | 'one_time_trigger' | 'recurring_trigger') || undefined, + config_type: (metadata.config_type as 'tool' | 'agent' | 'prompt' | 'pipeline' | 'start_agent' | 'one_time_trigger' | 'recurring_trigger' | 'external_trigger') || undefined, name: metadata.name } }; @@ -193,6 +204,8 @@ function AssistantMessage({ loading, onStatusBarChange, projectId, + triggers, + onTriggersUpdated, }: { content: z.infer['content'], workflow: z.infer, @@ -201,11 +214,24 @@ function AssistantMessage({ loading: boolean, onStatusBarChange?: (status: any) => void; projectId: string; + triggers?: CopilotTriggerType[]; + onTriggersUpdated?: () => Promise | void; }) { const blocks = useParsedBlocks(content); const [appliedActions, setAppliedActions] = useState>(new Set()); // Remove autoApplyEnabled and useEffect for auto-apply + const triggersRef = useRef(triggers); + const triggerUpdateCallbackRef = useRef(onTriggersUpdated); + + useEffect(() => { + triggersRef.current = triggers; + }, [triggers]); + + useEffect(() => { + triggerUpdateCallbackRef.current = onTriggersUpdated; + }, [onTriggersUpdated]); + // parse actions from parts const parsed = useMemo(() => { const result: z.infer[] = []; @@ -353,54 +379,192 @@ function AssistantMessage({ }, [dispatch, workflow.agents, workflow.tools]); const handleTriggerAction = useCallback(async (action: any): Promise => { - if (action.action !== 'create_new') { + const configType = action.config_type; + const actionType = action.action; + const triggerList = triggersRef.current ?? []; + + try { + if (configType === 'one_time_trigger') { + if (actionType === 'create_new') { + const { scheduledTime, input } = action.config_changes || {}; + if (!scheduledTime || !input) { + console.error('Missing scheduledTime or input for one-time trigger', action); + return false; + } + const { createScheduledJobRule } = await loadScheduledJobActions(); + await createScheduledJobRule({ + projectId, + scheduledTime, + input, + }); + return true; + } + + const target = triggerList.find( + (trigger): trigger is Extract, { type: 'one_time' }> => + trigger.type === 'one_time' && trigger.name === action.name + ); + + if (!target) { + console.warn('Unable to resolve one-time trigger for action', action.name); + return false; + } + + const { fetchScheduledJobRule, deleteScheduledJobRule, createScheduledJobRule } = await loadScheduledJobActions(); + + if (actionType === 'delete') { + await deleteScheduledJobRule({ projectId, ruleId: target.id }); + return true; + } + + if (actionType === 'edit') { + const existing = await fetchScheduledJobRule({ ruleId: target.id }); + if (!existing) { + console.error('Failed to load existing one-time trigger for edit', action.name); + return false; + } + + const scheduledTime = action.config_changes?.scheduledTime ?? existing.nextRunAt; + const input = action.config_changes?.input ?? existing.input; + + if (!scheduledTime || !input) { + console.error('Missing data for one-time trigger edit', action); + return false; + } + + const created = await createScheduledJobRule({ + projectId, + scheduledTime, + input, + }); + + // Remove the previous rule only after successfully creating the updated one + await deleteScheduledJobRule({ projectId, ruleId: target.id }); + + return Boolean(created?.id); + } + } + + if (configType === 'recurring_trigger') { + if (actionType === 'create_new') { + const { cron, input } = action.config_changes || {}; + if (!cron || !input) { + console.error('Missing cron or input for recurring trigger', action); + return false; + } + const { createRecurringJobRule } = await loadRecurringJobActions(); + await createRecurringJobRule({ + projectId, + cron, + input, + }); + return true; + } + + const target = triggerList.find( + (trigger): trigger is Extract, { type: 'recurring' }> => + trigger.type === 'recurring' && trigger.name === action.name + ); + + if (!target) { + console.warn('Unable to resolve recurring trigger for action', action.name); + return false; + } + + const { + fetchRecurringJobRule, + deleteRecurringJobRule, + createRecurringJobRule, + toggleRecurringJobRule, + } = await loadRecurringJobActions(); + + if (actionType === 'delete') { + await deleteRecurringJobRule({ projectId, ruleId: target.id }); + return true; + } + + if (actionType === 'edit') { + const existing = await fetchRecurringJobRule({ ruleId: target.id }); + if (!existing) { + console.error('Failed to load existing recurring trigger for edit', action.name); + return false; + } + + const desiredDisabled = typeof action.config_changes?.disabled === 'boolean' + ? action.config_changes.disabled + : existing.disabled; + + const hasCronChange = Object.prototype.hasOwnProperty.call(action.config_changes ?? {}, 'cron'); + const hasInputChange = Object.prototype.hasOwnProperty.call(action.config_changes ?? {}, 'input'); + const hasDisabledToggle = Object.prototype.hasOwnProperty.call(action.config_changes ?? {}, 'disabled'); + + if (!hasCronChange && !hasInputChange && hasDisabledToggle) { + if (desiredDisabled !== existing.disabled) { + await toggleRecurringJobRule({ ruleId: target.id, disabled: desiredDisabled }); + } + return true; + } + + const cron = action.config_changes?.cron ?? existing.cron; + const input = action.config_changes?.input ?? existing.input; + + if (!cron || !input) { + console.error('Missing data for recurring trigger edit', action); + return false; + } + + const created = await createRecurringJobRule({ + projectId, + cron, + input, + }); + + await deleteRecurringJobRule({ projectId, ruleId: target.id }); + + if (desiredDisabled !== created.disabled) { + await toggleRecurringJobRule({ ruleId: created.id, disabled: desiredDisabled }); + } + + return true; + } + } + + if (configType === 'external_trigger' && actionType === 'delete') { + const target = triggerList.find( + (trigger): trigger is Extract, { type: 'external' }> => + trigger.type === 'external' && trigger.triggerTypeName === action.name + ); + + if (!target) { + console.warn('Unable to resolve external trigger for action', action.name); + return false; + } + + const { deleteComposioTriggerDeployment } = await loadComposioActions(); + await deleteComposioTriggerDeployment({ projectId, deploymentId: target.id }); + return true; + } + } catch (error) { + console.error('Failed to handle trigger action', action, error); return false; } - if (action.config_type === 'one_time_trigger') { - const { scheduledTime, input } = action.config_changes || {}; - if (!scheduledTime || !input) { - console.error('Missing scheduledTime or input for one-time trigger', action); - return false; - } - try { - const { createScheduledJobRule } = await loadScheduledJobActions(); - await createScheduledJobRule({ - projectId, - scheduledTime, - input, - }); - return true; - } catch (error) { - console.error('Failed to create one-time trigger', error); - return false; - } - } - - if (action.config_type === 'recurring_trigger') { - const { cron, input } = action.config_changes || {}; - if (!cron || !input) { - console.error('Missing cron or input for recurring trigger', action); - return false; - } - try { - const { createRecurringJobRule } = await loadRecurringJobActions(); - await createRecurringJobRule({ - projectId, - cron, - input, - }); - return true; - } catch (error) { - console.error('Failed to create recurring trigger', error); - return false; - } - } - console.warn('Unhandled trigger action from Copilot applyAction', action); return false; }, [projectId]); + const refreshTriggers = useCallback(async () => { + const callback = triggerUpdateCallbackRef.current; + if (!callback) { + return; + } + try { + await callback(); + } catch (error) { + console.error('Failed to refresh triggers after Copilot action', error); + } + }, []); + // Memoized handleApplyAll for useEffect dependencies const handleApplyAll = useCallback(async () => { const unapplied = parsed.reduce['content']; actionIndex: number }>>((acc, part, idx) => { @@ -411,16 +575,20 @@ function AssistantMessage({ }, []); const newlyApplied: number[] = []; + let triggerMutated = false; for (const { action, actionIndex } of unapplied) { try { - const isTrigger = action.config_type === 'one_time_trigger' || action.config_type === 'recurring_trigger'; + const isTrigger = action.config_type === 'one_time_trigger' || action.config_type === 'recurring_trigger' || action.config_type === 'external_trigger'; const success = isTrigger ? await handleTriggerAction(action) : applyAction(action); if (success) { newlyApplied.push(actionIndex); + if (isTrigger) { + triggerMutated = true; + } } } catch (error) { console.error('Failed to apply Copilot action', action, error); @@ -434,7 +602,11 @@ function AssistantMessage({ return next; }); } - }, [parsed, appliedActions, applyAction, handleTriggerAction]); + + if (triggerMutated) { + await refreshTriggers(); + } + }, [parsed, appliedActions, applyAction, handleTriggerAction, refreshTriggers]); // Manual single apply (from card) const handleSingleApply = useCallback(async (action: z.infer['content'], actionIndex: number) => { @@ -443,18 +615,21 @@ function AssistantMessage({ } try { - const isTrigger = action.config_type === 'one_time_trigger' || action.config_type === 'recurring_trigger'; + const isTrigger = action.config_type === 'one_time_trigger' || action.config_type === 'recurring_trigger' || action.config_type === 'external_trigger'; const success = isTrigger ? await handleTriggerAction(action) : applyAction(action); if (success) { setAppliedActions(prev => new Set([...prev, actionIndex])); + if (isTrigger) { + await refreshTriggers(); + } } } catch (error) { console.error('Failed to apply Copilot action', action, error); } - }, [appliedActions, applyAction, handleTriggerAction]); + }, [appliedActions, applyAction, handleTriggerAction, refreshTriggers]); useEffect(() => { if (loading) { @@ -607,7 +782,9 @@ export function Messages({ dispatch, onStatusBarChange, toolCalling, - toolQuery + toolQuery, + triggers, + onTriggersUpdated, }: { projectId: string; messages: z.infer[]; @@ -618,6 +795,8 @@ export function Messages({ onStatusBarChange?: (status: any) => void; toolCalling?: boolean; toolQuery?: string | null; + triggers?: z.infer[]; + onTriggersUpdated?: () => Promise | void; }) { const messagesEndRef = useRef(null); const [displayMessages, setDisplayMessages] = useState(messages); @@ -660,6 +839,8 @@ export function Messages({ messageIndex={messageIndex} loading={loadingResponse} projectId={projectId} + triggers={triggers} + onTriggersUpdated={onTriggersUpdated} onStatusBarChange={status => { // Only update for the last assistant message if (messageIndex === displayMessages.length - 1) { diff --git a/apps/rowboat/app/projects/[projectId]/copilot/use-copilot.tsx b/apps/rowboat/app/projects/[projectId]/copilot/use-copilot.tsx index dd11bfbf..38cfcbf6 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/use-copilot.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/use-copilot.tsx @@ -3,6 +3,7 @@ import { getCopilotResponseStream } from "@/app/actions/copilot.actions"; import { CopilotMessage } from "@/src/entities/models/copilot"; import { Workflow } from "@/app/lib/types/workflow_types"; import { DataSource } from "@/src/entities/models/data-source"; +import { TriggerSchemaForCopilot } from "@/src/entities/models/copilot"; import { z } from "zod"; import { WithStringId } from "@/app/lib/types/types"; @@ -11,6 +12,7 @@ interface UseCopilotParams { workflow: z.infer; context: any; dataSources?: z.infer[]; + triggers?: z.infer[]; } interface UseCopilotResult { @@ -29,7 +31,7 @@ interface UseCopilotResult { cancel: () => void; } -export function useCopilot({ projectId, workflow, context, dataSources }: UseCopilotParams): UseCopilotResult { +export function useCopilot({ projectId, workflow, context, dataSources, triggers }: UseCopilotParams): UseCopilotResult { const [streamingResponse, setStreamingResponse] = useState(''); const [loading, setLoading] = useState(false); const [toolCalling, setToolCalling] = useState(false); @@ -77,7 +79,7 @@ export function useCopilot({ projectId, workflow, context, dataSources }: UseCop // Wait 2 rAF frames to let layout stabilize (avoids StrictMode/remount race on initial load) await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(() => resolve()))); - const res = await getCopilotResponseStream(projectId, messages, workflow, context || null, dataSources); + const res = await getCopilotResponseStream(projectId, messages, workflow, context || null, dataSources, triggers); // Check for billing error @@ -139,7 +141,7 @@ export function useCopilot({ projectId, workflow, context, dataSources }: UseCop setLoading(false); inFlightRef.current = false; } - }, [projectId, workflow, context, dataSources]); + }, [projectId, workflow, context, dataSources, triggers]); const cancel = useCallback(() => { cancelRef.current?.(); diff --git a/apps/rowboat/app/projects/[projectId]/workflow/app.tsx b/apps/rowboat/app/projects/[projectId]/workflow/app.tsx index 9d7a4c8b..89308ba5 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/app.tsx @@ -1,5 +1,6 @@ "use client"; import { DataSource } from "@/src/entities/models/data-source"; +import { TriggerSchemaForCopilot } from "@/src/entities/models/copilot"; import { Project } from "@/src/entities/models/project"; import { z } from "zod"; import { useCallback, useEffect, useState } from "react"; @@ -10,10 +11,15 @@ import { revertToLiveWorkflow } from "@/app/actions/project.actions"; import { fetchProject } from "@/app/actions/project.actions"; import { Workflow } from "@/app/lib/types/workflow_types"; import { ModelsResponse } from "@/app/lib/types/billing_types"; +import { listScheduledJobRules } from "@/app/actions/scheduled-job-rules.actions"; +import { listRecurringJobRules } from "@/app/actions/recurring-job-rules.actions"; +import { listComposioTriggerDeployments } from "@/app/actions/composio.actions"; +import { transformTriggersForCopilot, DEFAULT_TRIGGER_FETCH_LIMIT } from "./trigger-transform"; export function App({ initialProjectData, initialDataSources, + initialTriggers, eligibleModels, useRag, useRagUploads, @@ -24,6 +30,7 @@ export function App({ }: { initialProjectData: z.infer; initialDataSources: z.infer[]; + initialTriggers: z.infer[]; eligibleModels: z.infer | "*"; useRag: boolean; useRagUploads: boolean; @@ -44,6 +51,7 @@ export function App({ }); const [project, setProject] = useState>(initialProjectData); const [dataSources, setDataSources] = useState[]>(initialDataSources); + const [triggers, setTriggers] = useState[]>(initialTriggers); const [loading, setLoading] = useState(false); console.log('workflow app.tsx render'); @@ -65,21 +73,42 @@ export function App({ workflow = mode === 'live' ? project?.liveWorkflow : project?.draftWorkflow; } - const reloadData = useCallback(async () => { - setLoading(true); - const [ - projectData, - sourcesData, - ] = await Promise.all([ - fetchProject(initialProjectData.id), - listDataSources(initialProjectData.id), + const fetchTriggers = useCallback(async () => { + const [scheduled, recurring, composio] = await Promise.all([ + listScheduledJobRules({ projectId: initialProjectData.id, limit: DEFAULT_TRIGGER_FETCH_LIMIT }), + listRecurringJobRules({ projectId: initialProjectData.id, limit: DEFAULT_TRIGGER_FETCH_LIMIT }), + listComposioTriggerDeployments({ projectId: initialProjectData.id, limit: DEFAULT_TRIGGER_FETCH_LIMIT }), ]); - setProject(projectData); - setDataSources(sourcesData); - setLoading(false); + return transformTriggersForCopilot({ + scheduled: scheduled.items ?? [], + recurring: recurring.items ?? [], + composio: composio.items ?? [], + }); }, [initialProjectData.id]); + const refreshTriggers = useCallback(async () => { + const nextTriggers = await fetchTriggers(); + setTriggers(nextTriggers); + }, [fetchTriggers]); + + const reloadData = useCallback(async () => { + setLoading(true); + try { + const [projectData, sourcesData, triggerData] = await Promise.all([ + fetchProject(initialProjectData.id), + listDataSources(initialProjectData.id), + fetchTriggers(), + ]); + + setProject(projectData); + setDataSources(sourcesData); + setTriggers(triggerData); + } finally { + setLoading(false); + } + }, [fetchTriggers, initialProjectData.id]); + const handleProjectToolsUpdate = useCallback(async () => { // Lightweight refresh for tool-only updates const projectConfig = await fetchProject(initialProjectData.id); @@ -133,8 +162,12 @@ export function App({ async function handleRevertToLive() { setLoading(true); - await revertToLiveWorkflow(initialProjectData.id); - reloadData(); + try { + await revertToLiveWorkflow(initialProjectData.id); + await reloadData(); + } finally { + setLoading(false); + } } // if workflow is null, show the selector @@ -152,6 +185,7 @@ export function App({ onToggleAutoPublish={handleToggleAutoPublish} workflow={workflow} dataSources={dataSources} + triggers={triggers} projectConfig={project} useRag={useRag} useRagUploads={useRagUploads} @@ -164,6 +198,7 @@ export function App({ onProjectToolsUpdated={handleProjectToolsUpdate} onDataSourcesUpdated={handleDataSourcesUpdate} onProjectConfigUpdated={handleProjectConfigUpdate} + onTriggersUpdated={refreshTriggers} chatWidgetHost={chatWidgetHost} />} diff --git a/apps/rowboat/app/projects/[projectId]/workflow/page.tsx b/apps/rowboat/app/projects/[projectId]/workflow/page.tsx index 864d31c4..d855c21b 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/page.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/page.tsx @@ -9,10 +9,17 @@ import { ModelsResponse } from "@/app/lib/types/billing_types"; import { requireAuth } from "@/app/lib/auth"; import { IFetchProjectController } from "@/src/interface-adapters/controllers/projects/fetch-project.controller"; import { IListDataSourcesController } from "@/src/interface-adapters/controllers/data-sources/list-data-sources.controller"; +import { IListScheduledJobRulesController } from "@/src/interface-adapters/controllers/scheduled-job-rules/list-scheduled-job-rules.controller"; +import { IListRecurringJobRulesController } from "@/src/interface-adapters/controllers/recurring-job-rules/list-recurring-job-rules.controller"; +import { IListComposioTriggerDeploymentsController } from "@/src/interface-adapters/controllers/composio-trigger-deployments/list-composio-trigger-deployments.controller"; import { z } from "zod"; +import { transformTriggersForCopilot, DEFAULT_TRIGGER_FETCH_LIMIT } from "./trigger-transform"; const fetchProjectController = container.resolve('fetchProjectController'); const listDataSourcesController = container.resolve('listDataSourcesController'); +const listScheduledJobRulesController = container.resolve('listScheduledJobRulesController'); +const listRecurringJobRulesController = container.resolve('listRecurringJobRulesController'); +const listComposioTriggerDeploymentsController = container.resolve('listComposioTriggerDeploymentsController'); const DEFAULT_MODEL = process.env.PROVIDER_DEFAULT_MODEL || "gpt-4.1"; @@ -39,23 +46,50 @@ export default async function Page( notFound(); } - const sources = await listDataSourcesController.execute({ - caller: "user", - userId: user.id, - projectId: params.projectId, - }); + const [sources, scheduledTriggers, recurringTriggers, composioTriggers] = await Promise.all([ + listDataSourcesController.execute({ + caller: "user", + userId: user.id, + projectId: params.projectId, + }), + listScheduledJobRulesController.execute({ + caller: "user", + userId: user.id, + projectId: params.projectId, + limit: DEFAULT_TRIGGER_FETCH_LIMIT, + }), + listRecurringJobRulesController.execute({ + caller: "user", + userId: user.id, + projectId: params.projectId, + limit: DEFAULT_TRIGGER_FETCH_LIMIT, + }), + listComposioTriggerDeploymentsController.execute({ + caller: "user", + userId: user.id, + projectId: params.projectId, + limit: DEFAULT_TRIGGER_FETCH_LIMIT, + }), + ]); let eligibleModels: z.infer | "*" = '*'; if (USE_BILLING) { eligibleModels = await getEligibleModels(customer.id); } + const triggers = transformTriggersForCopilot({ + scheduled: scheduledTriggers.items ?? [], + recurring: recurringTriggers.items ?? [], + composio: composioTriggers.items ?? [], + }); + console.log('/workflow page.tsx serve'); return ( ; + +interface TransformParams { + scheduled: Array<{ + id: string; + nextRunAt: string; + status: 'pending' | 'processing' | 'triggered'; + input?: { messages: Array> }; + }>; + recurring: Array<{ + id: string; + cron: string; + nextRunAt: string | null; + disabled: boolean; + input?: { messages: Array> }; + }>; + composio: Array<{ + id: string; + triggerTypeName: string; + toolkitSlug: string; + triggerTypeSlug: string; + triggerConfig: Record; + }>; +} + +export function transformTriggersForCopilot({ + scheduled, + recurring, + composio, +}: TransformParams): CopilotTrigger[] { + const placeholderInput = { + messages: [ + { + role: "user" as const, + content: "Trigger execution", + }, + ], + } satisfies { messages: Array> }; + + const oneTime = scheduled.map((trigger) => ({ + type: "one_time" as const, + id: trigger.id, + name: `One-time trigger (${new Date(trigger.nextRunAt).toLocaleDateString('en-US')})`, + nextRunAt: trigger.nextRunAt, + status: trigger.status, + input: trigger.input ?? placeholderInput, + })); + + const recurringTriggers = recurring.map((trigger) => ({ + type: "recurring" as const, + id: trigger.id, + name: `Recurring trigger (${trigger.cron})`, + cron: trigger.cron, + nextRunAt: trigger.nextRunAt ?? '', + disabled: trigger.disabled, + input: trigger.input ?? placeholderInput, + })); + + const external = composio.map((trigger) => ({ + type: "external" as const, + id: trigger.id, + triggerTypeName: trigger.triggerTypeName, + toolkitSlug: trigger.toolkitSlug, + triggerTypeSlug: trigger.triggerTypeSlug, + triggerConfig: trigger.triggerConfig, + })); + + return [...oneTime, ...recurringTriggers, ...external] as CopilotTrigger[]; +} diff --git a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx index 476d8366..6ef91e45 100644 --- a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx +++ b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx @@ -3,6 +3,7 @@ import React, { useReducer, Reducer, useState, useCallback, useEffect, useRef, c import { MCPServer, Message, WithStringId } from "../../../lib/types/types"; import { Workflow, WorkflowTool, WorkflowPrompt, WorkflowAgent, WorkflowPipeline } from "../../../lib/types/workflow_types"; import { DataSource } from "@/src/entities/models/data-source"; +import { TriggerSchemaForCopilot } from "@/src/entities/models/copilot"; import { Project } from "@/src/entities/models/project"; import { produce, applyPatches, enablePatches, produceWithPatches, Patch } from 'immer'; import { AgentConfig } from "../entities/agent_config"; @@ -962,6 +963,7 @@ export function useEntitySelection() { export function WorkflowEditor({ projectId, dataSources, + triggers, workflow, useRag, useRagUploads, @@ -978,10 +980,12 @@ export function WorkflowEditor({ onProjectToolsUpdated, onDataSourcesUpdated, onProjectConfigUpdated, + onTriggersUpdated, chatWidgetHost, }: { projectId: string; dataSources: z.infer[]; + triggers: z.infer[]; workflow: z.infer; useRag: boolean; useRagUploads: boolean; @@ -998,6 +1002,7 @@ export function WorkflowEditor({ onProjectToolsUpdated?: () => void; onDataSourcesUpdated?: () => void; onProjectConfigUpdated?: () => void; + onTriggersUpdated?: () => Promise | void; chatWidgetHost: string; }) { @@ -2313,8 +2318,10 @@ export function WorkflowEditor({ } isInitialState={isInitialState} dataSources={dataSources} + triggers={triggers} activePanel={activePanel} onTogglePanel={handleTogglePanel} + onTriggersUpdated={onTriggersUpdated} /> {/* Config overlay above Copilot when agents + skipper layout is active */} {state.present.selection && viewMode === 'two_agents_skipper' && ( diff --git a/apps/rowboat/src/application/lib/copilot/copilot.ts b/apps/rowboat/src/application/lib/copilot/copilot.ts index 7042a0dc..4d188405 100644 --- a/apps/rowboat/src/application/lib/copilot/copilot.ts +++ b/apps/rowboat/src/application/lib/copilot/copilot.ts @@ -2,7 +2,7 @@ import z from "zod"; import { createOpenAI } from "@ai-sdk/openai"; import { generateObject, streamText, tool } from "ai"; import { Workflow, WorkflowTool } from "@/app/lib/types/workflow_types"; -import { CopilotChatContext, CopilotMessage, DataSourceSchemaForCopilot } from "../../../entities/models/copilot"; +import { CopilotChatContext, CopilotMessage, DataSourceSchemaForCopilot, TriggerSchemaForCopilot } from "../../../entities/models/copilot"; import { PrefixLogger } from "@/app/lib/utils"; import zodToJsonSchema from "zod-to-json-schema"; import { COPILOT_INSTRUCTIONS_EDIT_AGENT } from "./copilot_edit_agent"; @@ -100,6 +100,51 @@ function getCurrentTimePrompt(): string { return `**CURRENT TIME**: ${new Date().toISOString()}`; } +function getTriggersPrompt(triggers: z.infer[]): string { + if (!triggers || triggers.length === 0) { + return ''; + } + + const simplifiedTriggers = triggers.map(trigger => { + if (trigger.type === 'one_time') { + return { + id: trigger.id, + type: 'one_time', + name: trigger.name, + scheduledTime: trigger.nextRunAt, + input: trigger.input, + status: trigger.status, + }; + } else if (trigger.type === 'recurring') { + return { + id: trigger.id, + type: 'recurring', + name: trigger.name, + cron: trigger.cron, + nextRunAt: trigger.nextRunAt, + disabled: trigger.disabled, + input: trigger.input, + }; + } else { + return { + id: trigger.id, + type: 'external', + name: trigger.triggerTypeName, + toolkit: trigger.toolkitSlug, + triggerType: trigger.triggerTypeSlug, + config: trigger.triggerConfig, + }; + } + }); + + return `**NOTE**: +The following triggers are currently configured: +\`\`\`json +${JSON.stringify(simplifiedTriggers)} +\`\`\` +`; +} + async function searchRelevantTools(usageTracker: UsageTracker, query: string): Promise { const logger = new PrefixLogger("copilot-search-tools"); console.log("🔧 TOOL CALL: searchRelevantTools", { query }); @@ -189,10 +234,11 @@ function updateLastUserMessage( contextPrompt: string, dataSourcesPrompt: string = '', timePrompt: string = '', + triggersPrompt: string = '', ): void { const lastMessage = messages[messages.length - 1]; if (lastMessage.role === 'user') { - lastMessage.content = `${currentWorkflowPrompt}\n\n${contextPrompt}\n\n${dataSourcesPrompt}\n\n${timePrompt}\n\nUser: ${JSON.stringify(lastMessage.content)}`; + lastMessage.content = `${currentWorkflowPrompt}\n\n${contextPrompt}\n\n${dataSourcesPrompt}\n\n${timePrompt}\n\n${triggersPrompt}\n\nUser: ${JSON.stringify(lastMessage.content)}`; } } @@ -202,6 +248,7 @@ export async function getEditAgentInstructionsResponse( context: z.infer | null, messages: z.infer[], workflow: z.infer, + triggers: z.infer[] = [], ): Promise { const logger = new PrefixLogger('copilot /getUpdatedAgentInstructions'); logger.log('context', context); @@ -216,8 +263,11 @@ export async function getEditAgentInstructionsResponse( // set time prompt let timePrompt = getCurrentTimePrompt(); + // set triggers prompt + let triggersPrompt = getTriggersPrompt(triggers); + // add the above prompts to the last user message - updateLastUserMessage(messages, currentWorkflowPrompt, contextPrompt, '', timePrompt); + updateLastUserMessage(messages, currentWorkflowPrompt, contextPrompt, '', timePrompt, triggersPrompt); // call model console.log("calling model", JSON.stringify({ @@ -257,7 +307,8 @@ export async function* streamMultiAgentResponse( context: z.infer | null, messages: z.infer[], workflow: z.infer, - dataSources: z.infer[] + dataSources: z.infer[], + triggers: z.infer[] = [] ): AsyncIterable> { const logger = new PrefixLogger('copilot /stream'); logger.log('context', context); @@ -282,8 +333,11 @@ export async function* streamMultiAgentResponse( // set time prompt let timePrompt = getCurrentTimePrompt(); + // set triggers prompt + let triggersPrompt = getTriggersPrompt(triggers); + // add the above prompts to the last user message - updateLastUserMessage(messages, currentWorkflowPrompt, contextPrompt, dataSourcesPrompt, timePrompt); + updateLastUserMessage(messages, currentWorkflowPrompt, contextPrompt, dataSourcesPrompt, timePrompt, triggersPrompt); // call model console.log("🤖 AI MODEL CALL STARTED", { 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 4a078a42..83787e7f 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 @@ -259,6 +259,39 @@ Recurring trigger example (COPY THIS EXACT FORMAT): } } +### Editing and Deleting Triggers + +You can also edit or delete existing triggers that are shown in the current workflow context. + +Edit trigger example: +// action: edit +// config_type: recurring_trigger +// name: Daily Status Check +{ + "change_description": "Update the daily status check to run at 10 AM instead of 9 AM", + "config_changes": { + "cron": "0 10 * * *" + } +} + +Delete trigger example: +// action: delete +// config_type: one_time_trigger +// name: Weekly Report - Dec 15 +{ + "change_description": "Remove the one-time trigger for weekly report as it's no longer needed" +} + +### External Triggers + +External triggers (from Composio integrations) can also be deleted: +// action: delete +// config_type: external_trigger +// name: Slack Message Received +{ + "change_description": "Remove the Slack message trigger as we're switching to a different notification system" +} + diff --git a/apps/rowboat/src/application/use-cases/copilot/create-copilot-cached-turn.use-case.ts b/apps/rowboat/src/application/use-cases/copilot/create-copilot-cached-turn.use-case.ts index 20f70a92..bb8b4cd2 100644 --- a/apps/rowboat/src/application/use-cases/copilot/create-copilot-cached-turn.use-case.ts +++ b/apps/rowboat/src/application/use-cases/copilot/create-copilot-cached-turn.use-case.ts @@ -3,7 +3,7 @@ import { nanoid } from 'nanoid'; import { ICacheService } from '@/src/application/services/cache.service.interface'; import { IUsageQuotaPolicy } from '@/src/application/policies/usage-quota.policy.interface'; import { IProjectActionAuthorizationPolicy } from '@/src/application/policies/project-action-authorization.policy'; -import { CopilotChatContext, CopilotMessage, DataSourceSchemaForCopilot } from '@/src/entities/models/copilot'; +import { CopilotChatContext, CopilotMessage, DataSourceSchemaForCopilot, TriggerSchemaForCopilot } from '@/src/entities/models/copilot'; import { Workflow } from '@/app/lib/types/workflow_types'; import { USE_BILLING } from "@/app/lib/feature_flags"; import { authorize, getCustomerIdForProject } from "@/app/lib/billing"; @@ -19,6 +19,7 @@ const inputSchema = z.object({ workflow: Workflow, context: CopilotChatContext.nullable(), dataSources: z.array(DataSourceSchemaForCopilot).optional(), + triggers: z.array(TriggerSchemaForCopilot).optional(), }), }); diff --git a/apps/rowboat/src/application/use-cases/copilot/run-copilot-cached-turn.use-case.ts b/apps/rowboat/src/application/use-cases/copilot/run-copilot-cached-turn.use-case.ts index 7afb0a29..334c8b6d 100644 --- a/apps/rowboat/src/application/use-cases/copilot/run-copilot-cached-turn.use-case.ts +++ b/apps/rowboat/src/application/use-cases/copilot/run-copilot-cached-turn.use-case.ts @@ -90,6 +90,7 @@ export class RunCopilotCachedTurnUseCase implements IRunCopilotCachedTurnUseCase cachedTurn.messages, cachedTurn.workflow, cachedTurn.dataSources || [], + cachedTurn.triggers || [], )) { yield event; } diff --git a/apps/rowboat/src/entities/models/copilot.ts b/apps/rowboat/src/entities/models/copilot.ts index 6317a787..972b5948 100644 --- a/apps/rowboat/src/entities/models/copilot.ts +++ b/apps/rowboat/src/entities/models/copilot.ts @@ -2,6 +2,9 @@ import { z } from "zod"; import { Workflow } from "@/app/lib/types/workflow_types"; import { Message } from "@/app/lib/types/types"; import { DataSource } from "@/src/entities/models/data-source"; +import { ScheduledJobRule } from "@/src/entities/models/scheduled-job-rule"; +import { RecurringJobRule } from "@/src/entities/models/recurring-job-rule"; +import { ComposioTriggerDeployment } from "@/src/entities/models/composio-trigger-deployment"; export const DataSourceSchemaForCopilot = DataSource.pick({ id: true, @@ -10,6 +13,43 @@ export const DataSourceSchemaForCopilot = DataSource.pick({ data: true, }); +export const ScheduledJobRuleSchemaForCopilot = ScheduledJobRule.pick({ + id: true, + nextRunAt: true, + status: true, + input: true, +}).extend({ + type: z.literal('one_time'), + name: z.string(), +}); + +export const RecurringJobRuleSchemaForCopilot = RecurringJobRule.pick({ + id: true, + cron: true, + nextRunAt: true, + disabled: true, + input: true, +}).extend({ + type: z.literal('recurring'), + name: z.string(), +}); + +export const ComposioTriggerDeploymentSchemaForCopilot = ComposioTriggerDeployment.pick({ + id: true, + triggerTypeName: true, + toolkitSlug: true, + triggerTypeSlug: true, + triggerConfig: true, +}).extend({ + type: z.literal('external'), +}); + +export const TriggerSchemaForCopilot = z.union([ + ScheduledJobRuleSchemaForCopilot, + RecurringJobRuleSchemaForCopilot, + ComposioTriggerDeploymentSchemaForCopilot, +]); + export const CopilotUserMessage = z.object({ role: z.literal('user'), content: z.string(), @@ -21,7 +61,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', 'one_time_trigger', 'recurring_trigger']), + config_type: z.enum(['tool', 'agent', 'prompt', 'pipeline', 'start_agent', 'one_time_trigger', 'recurring_trigger', 'external_trigger']), action: z.enum(['create_new', 'edit', 'delete']), name: z.string(), change_description: z.string(), @@ -60,6 +100,7 @@ export const CopilotAPIRequest = z.object({ workflow: Workflow, context: CopilotChatContext.nullable(), dataSources: z.array(DataSourceSchemaForCopilot).optional(), + triggers: z.array(TriggerSchemaForCopilot).optional(), }); export const CopilotAPIResponse = z.union([ z.object({ diff --git a/apps/rowboat/src/interface-adapters/controllers/copilot/create-copilot-cached-turn.controller.ts b/apps/rowboat/src/interface-adapters/controllers/copilot/create-copilot-cached-turn.controller.ts index bd14730e..80590539 100644 --- a/apps/rowboat/src/interface-adapters/controllers/copilot/create-copilot-cached-turn.controller.ts +++ b/apps/rowboat/src/interface-adapters/controllers/copilot/create-copilot-cached-turn.controller.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { CopilotChatContext, CopilotMessage, DataSourceSchemaForCopilot } from '@/src/entities/models/copilot'; +import { CopilotChatContext, CopilotMessage, DataSourceSchemaForCopilot, TriggerSchemaForCopilot } from '@/src/entities/models/copilot'; import { Workflow } from '@/app/lib/types/workflow_types'; import { ICreateCopilotCachedTurnUseCase } from "@/src/application/use-cases/copilot/create-copilot-cached-turn.use-case"; import { BadRequestError } from "@/src/entities/errors/common"; @@ -14,6 +14,7 @@ const inputSchema = z.object({ workflow: Workflow, context: CopilotChatContext.nullable(), dataSources: z.array(DataSourceSchemaForCopilot).optional(), + triggers: z.array(TriggerSchemaForCopilot).optional(), }), });