From 96981ae1ee38f988a8ec70d078f4c862e97e605a Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Fri, 1 Aug 2025 01:09:38 +0000 Subject: [PATCH 01/19] Apply patch [skip ci] --- apps/web/src/types/cron.ts | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 apps/web/src/types/cron.ts diff --git a/apps/web/src/types/cron.ts b/apps/web/src/types/cron.ts new file mode 100644 index 00000000..2fd20b29 --- /dev/null +++ b/apps/web/src/types/cron.ts @@ -0,0 +1,63 @@ +/** + * Types for cron job functionality in Open Agent Platform + * These types align with the LangGraph SDK cron API + */ + +/** + * Metadata for a cron job + * The owner field is required to track which user created the cron + */ +export interface CronMetadata { + owner: string; // Supabase user ID + name?: string; // User-friendly name for the cron job + [key: string]: any; // Allow additional metadata fields +} + +/** + * Input structure for creating or updating a cron job + */ +export interface CronInput { + schedule: string; // Cron expression (e.g., "0 9 * * *" for 9 AM daily) + input: { + messages: Array<{ + role: "user" | "assistant" | "system"; + content: string; + }>; + [key: string]: any; // Allow additional input fields + }; + metadata?: CronMetadata; +} + +/** + * Complete cron job object as returned by the API + */ +export interface Cron { + cron_id: string; + assistant_id: string; + thread_id?: string; // Optional - only present for thread-specific crons + schedule: string; + input: CronInput["input"]; + metadata?: CronMetadata; + created_at?: string; + updated_at?: string; + next_run_at?: string; + last_run_at?: string; +} + +/** + * Input for creating a new cron job with user-friendly fields + */ +export interface CreateCronFormData { + name: string; + schedule: string; + inputMessage: string; +} + +/** + * Input for updating an existing cron job + */ +export interface UpdateCronInput { + schedule?: string; + input?: CronInput["input"]; + metadata?: Partial; +} From 9dd0259514f04d609d8dfec803a068ed38593716 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Fri, 1 Aug 2025 01:10:24 +0000 Subject: [PATCH 02/19] Apply patch [skip ci] --- apps/web/src/hooks/use-crons.tsx | 200 +++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 apps/web/src/hooks/use-crons.tsx diff --git a/apps/web/src/hooks/use-crons.tsx b/apps/web/src/hooks/use-crons.tsx new file mode 100644 index 00000000..36587bb4 --- /dev/null +++ b/apps/web/src/hooks/use-crons.tsx @@ -0,0 +1,200 @@ +import { useState, useCallback } from "react"; +import { createClient } from "@/lib/client"; +import { useAuthContext } from "@/providers/Auth"; +import { Cron, CronInput, CreateCronFormData } from "@/types/cron"; +import { toast } from "sonner"; + +/** + * Custom hook for managing cron job operations + * Provides functions to list, create, update, and delete cron jobs + */ +export function useCrons() { + const { session, user } = useAuthContext(); + const [loading, setLoading] = useState(false); + const [crons, setCrons] = useState([]); + + /** + * List all crons for a specific assistant + */ + const listCrons = useCallback( + async (assistantId: string, deploymentId: string) => { + if (!session?.access_token) { + toast.error("Authentication required"); + return []; + } + + setLoading(true); + try { + const client = createClient(deploymentId, session.access_token); + + // Get all crons and filter by assistant_id and owner + const response = await client.crons.list(); + const userCrons = response.filter( + (cron: Cron) => + cron.assistant_id === assistantId && + cron.metadata?.owner === user?.id + ); + + setCrons(userCrons); + return userCrons; + } catch (error) { + console.error("Failed to list crons:", error); + toast.error("Failed to load cron jobs"); + return []; + } finally { + setLoading(false); + } + }, + [session, user] + ); + + /** + * Create a new cron job + */ + const createCron = useCallback( + async ( + assistantId: string, + deploymentId: string, + formData: CreateCronFormData + ) => { + if (!session?.access_token || !user?.id) { + toast.error("Authentication required"); + return null; + } + + setLoading(true); + try { + const client = createClient(deploymentId, session.access_token); + + const cronInput: CronInput = { + schedule: formData.schedule, + input: { + messages: [ + { + role: "user", + content: formData.inputMessage, + }, + ], + }, + metadata: { + owner: user.id, + name: formData.name, + }, + }; + + const newCron = await client.crons.create(assistantId, cronInput); + + toast.success("Cron job created successfully"); + + // Refresh the crons list + await listCrons(assistantId, deploymentId); + + return newCron; + } catch (error) { + console.error("Failed to create cron:", error); + toast.error("Failed to create cron job"); + return null; + } finally { + setLoading(false); + } + }, + [session, user, listCrons] + ); + + /** + * Update an existing cron job + */ + const updateCron = useCallback( + async ( + cronId: string, + deploymentId: string, + assistantId: string, + formData: CreateCronFormData + ) => { + if (!session?.access_token || !user?.id) { + toast.error("Authentication required"); + return null; + } + + setLoading(true); + try { + const client = createClient(deploymentId, session.access_token); + + // LangGraph SDK update method expects the full cron input + const updateInput: CronInput = { + schedule: formData.schedule, + input: { + messages: [ + { + role: "user", + content: formData.inputMessage, + }, + ], + }, + metadata: { + owner: user.id, + name: formData.name, + }, + }; + + const updatedCron = await client.crons.update(cronId, updateInput); + + toast.success("Cron job updated successfully"); + + // Refresh the crons list + await listCrons(assistantId, deploymentId); + + return updatedCron; + } catch (error) { + console.error("Failed to update cron:", error); + toast.error("Failed to update cron job"); + return null; + } finally { + setLoading(false); + } + }, + [session, user, listCrons] + ); + + /** + * Delete a cron job + */ + const deleteCron = useCallback( + async (cronId: string, deploymentId: string, assistantId: string) => { + if (!session?.access_token) { + toast.error("Authentication required"); + return false; + } + + setLoading(true); + try { + const client = createClient(deploymentId, session.access_token); + + await client.crons.delete(cronId); + + toast.success("Cron job deleted successfully"); + + // Refresh the crons list + await listCrons(assistantId, deploymentId); + + return true; + } catch (error) { + console.error("Failed to delete cron:", error); + toast.error("Failed to delete cron job"); + return false; + } finally { + setLoading(false); + } + }, + [session, listCrons] + ); + + return { + crons, + loading, + listCrons, + createCron, + updateCron, + deleteCron, + }; +} From 299da46e91aba04429959b58bb63f8c3e53980eb Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Fri, 1 Aug 2025 01:10:59 +0000 Subject: [PATCH 03/19] Apply patch [skip ci] --- apps/web/src/features/agents/components/agent-card.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/features/agents/components/agent-card.tsx b/apps/web/src/features/agents/components/agent-card.tsx index f4c04f31..2a2acba8 100644 --- a/apps/web/src/features/agents/components/agent-card.tsx +++ b/apps/web/src/features/agents/components/agent-card.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import { Bot, Brain, + Clock, Cloud, Edit, MessageSquare, @@ -160,3 +161,4 @@ export function AgentCard({ agent, showDeployment }: AgentCardProps) { ); } + From 84526cf5bf310ac753ffdf3c1613d7d43bb2d2af Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Fri, 1 Aug 2025 01:11:12 +0000 Subject: [PATCH 04/19] Apply patch [skip ci] --- apps/web/src/features/agents/components/agent-card.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/features/agents/components/agent-card.tsx b/apps/web/src/features/agents/components/agent-card.tsx index 2a2acba8..cf203c32 100644 --- a/apps/web/src/features/agents/components/agent-card.tsx +++ b/apps/web/src/features/agents/components/agent-card.tsx @@ -68,6 +68,7 @@ interface AgentCardProps { export function AgentCard({ agent, showDeployment }: AgentCardProps) { const [showEditDialog, setShowEditDialog] = useState(false); + const [showCronDialog, setShowCronDialog] = useState(false); const deployments = getDeployments(); const selectedDeployment = deployments.find( (d) => d.id === agent.deploymentId, @@ -162,3 +163,4 @@ export function AgentCard({ agent, showDeployment }: AgentCardProps) { ); } + From 6028d9beb455ebab0e6a8f7f454d71e22f26cc03 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Fri, 1 Aug 2025 01:11:31 +0000 Subject: [PATCH 05/19] Apply patch [skip ci] --- .../features/agents/components/agent-card.tsx | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/apps/web/src/features/agents/components/agent-card.tsx b/apps/web/src/features/agents/components/agent-card.tsx index cf203c32..926f3449 100644 --- a/apps/web/src/features/agents/components/agent-card.tsx +++ b/apps/web/src/features/agents/components/agent-card.tsx @@ -133,16 +133,28 @@ export function AgentCard({ agent, showDeployment }: AgentCardProps) { - {!isDefaultAgent && ( - - )} +
+ {!isDefaultAgent && ( + <> + + + + )} +
Date: Fri, 1 Aug 2025 01:11:49 +0000 Subject: [PATCH 06/19] Apply patch [skip ci] --- apps/web/src/features/agents/components/agent-card.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/src/features/agents/components/agent-card.tsx b/apps/web/src/features/agents/components/agent-card.tsx index 926f3449..8efa865e 100644 --- a/apps/web/src/features/agents/components/agent-card.tsx +++ b/apps/web/src/features/agents/components/agent-card.tsx @@ -26,6 +26,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { isUserCreatedDefaultAssistant } from "@/lib/agent-utils"; +import { CronDialog } from "@/features/crons/components/cron-dialog"; function SupportedConfigBadge({ type, @@ -177,3 +178,4 @@ export function AgentCard({ agent, showDeployment }: AgentCardProps) { + From 69cbd90791d4031fe9c3996c717a2751f2707ac1 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Fri, 1 Aug 2025 01:12:00 +0000 Subject: [PATCH 07/19] Apply patch [skip ci] --- apps/web/src/features/agents/components/agent-card.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/web/src/features/agents/components/agent-card.tsx b/apps/web/src/features/agents/components/agent-card.tsx index 8efa865e..f36f153d 100644 --- a/apps/web/src/features/agents/components/agent-card.tsx +++ b/apps/web/src/features/agents/components/agent-card.tsx @@ -172,6 +172,11 @@ export function AgentCard({ agent, showDeployment }: AgentCardProps) { open={showEditDialog} onOpenChange={(c) => setShowEditDialog(c)} /> + setShowCronDialog(c)} + /> ); } @@ -179,3 +184,4 @@ export function AgentCard({ agent, showDeployment }: AgentCardProps) { + From d4348a6178ee9d4d7bf5e02bb2982f629ceffdf7 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Fri, 1 Aug 2025 01:12:54 +0000 Subject: [PATCH 08/19] Apply patch [skip ci] --- .../features/crons/components/cron-dialog.tsx | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 apps/web/src/features/crons/components/cron-dialog.tsx diff --git a/apps/web/src/features/crons/components/cron-dialog.tsx b/apps/web/src/features/crons/components/cron-dialog.tsx new file mode 100644 index 00000000..28cd058b --- /dev/null +++ b/apps/web/src/features/crons/components/cron-dialog.tsx @@ -0,0 +1,176 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + AlertDialog, + AlertDialogContent, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Button } from "@/components/ui/button"; +import { Agent } from "@/types/agent"; +import { useCrons } from "@/hooks/use-crons"; +import { Plus, Edit, Trash2, Loader2 } from "lucide-react"; +import { Cron } from "@/types/cron"; + +interface CronDialogProps { + agent: Agent; + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function CronDialog({ agent, open, onOpenChange }: CronDialogProps) { + const { crons, loading, listCrons } = useCrons(); + const [showCreateForm, setShowCreateForm] = useState(false); + const [editingCron, setEditingCron] = useState(null); + + // Fetch crons when dialog opens + useEffect(() => { + if (open && agent.assistant_id && agent.deploymentId) { + listCrons(agent.assistant_id, agent.deploymentId); + } + }, [open, agent.assistant_id, agent.deploymentId, listCrons]); + + // Helper function to format the input message + const getInputMessage = (cron: Cron) => { + const messages = cron.input?.messages || []; + const firstMessage = messages.find((msg) => msg.role === "user"); + return firstMessage?.content || "No message"; + }; + + // Helper function to format the cron schedule in a readable way + const formatSchedule = (schedule: string) => { + // Basic cron schedule descriptions + const commonPatterns: Record = { + "0 * * * *": "Every hour", + "0 0 * * *": "Daily at midnight", + "0 9 * * *": "Daily at 9 AM", + "0 0 * * 0": "Weekly on Sunday", + "0 0 1 * *": "Monthly on the 1st", + "*/5 * * * *": "Every 5 minutes", + "*/15 * * * *": "Every 15 minutes", + "*/30 * * * *": "Every 30 minutes", + }; + + return commonPatterns[schedule] || schedule; + }; + + return ( + + + + Cron Jobs for {agent.name} + + +
+
+ {/* Create button */} +
+ +
+ + {/* Crons table */} + {loading ? ( +
+ +
+ ) : crons.length === 0 ? ( +
+ No cron jobs found. Create one to get started. +
+ ) : ( + + + + Name + Schedule + Input Message + Actions + + + + {crons.map((cron) => ( + + + {cron.metadata?.name || "Unnamed"} + + +
+
+ {cron.schedule} +
+
+ {formatSchedule(cron.schedule)} +
+
+
+ + {getInputMessage(cron)} + + +
+ + +
+
+
+ ))} +
+
+ )} +
+
+ + {/* Placeholder for form - will be implemented in task 4 */} + {(showCreateForm || editingCron) && ( +
+

+ Cron form will be implemented in the next task +

+ +
+ )} +
+
+ ); +} From f28d5bb70c3270088ab84fdcdbbb49d86aa09b12 Mon Sep 17 00:00:00 2001 From: "open-swe[bot]" Date: Fri, 1 Aug 2025 01:13:45 +0000 Subject: [PATCH 09/19] Apply patch [skip ci] --- .../features/crons/components/cron-form.tsx | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 apps/web/src/features/crons/components/cron-form.tsx diff --git a/apps/web/src/features/crons/components/cron-form.tsx b/apps/web/src/features/crons/components/cron-form.tsx new file mode 100644 index 00000000..c86a1044 --- /dev/null +++ b/apps/web/src/features/crons/components/cron-form.tsx @@ -0,0 +1,175 @@ +"use client"; + +import { useEffect } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { CreateCronFormData, Cron } from "@/types/cron"; +import { Agent } from "@/types/agent"; +import { useCrons } from "@/hooks/use-crons"; +import { toast } from "sonner"; +import { Loader2 } from "lucide-react"; + +interface CronFormProps { + agent: Agent; + cron?: Cron | null; + onSuccess?: () => void; + onCancel?: () => void; +} + +export function CronForm({ agent, cron, onSuccess, onCancel }: CronFormProps) { + const { createCron, updateCron, loading } = useCrons(); + + const form = useForm({ + defaultValues: { + name: "", + schedule: "", + inputMessage: "", + }, + }); + + // Pre-fill form when editing + useEffect(() => { + if (cron) { + form.reset({ + name: cron.metadata?.name || "", + schedule: cron.schedule || "", + inputMessage: cron.input?.messages?.[0]?.content || "", + }); + } + }, [cron, form]); + + const onSubmit = async (data: CreateCronFormData) => { + // Validate required fields + if (!data.name.trim()) { + toast.error("Name is required"); + return; + } + if (!data.schedule.trim()) { + toast.error("Schedule is required"); + return; + } + if (!data.inputMessage.trim()) { + toast.error("Input message is required"); + return; + } + + // Validate cron expression format (basic validation) + const cronParts = data.schedule.trim().split(" "); + if (cronParts.length !== 5) { + toast.error("Invalid cron expression. Must have 5 parts (minute hour day month weekday)"); + return; + } + + try { + if (cron) { + // Update existing cron + const result = await updateCron( + cron.cron_id, + agent.deploymentId, + agent.assistant_id, + data + ); + if (result) { + onSuccess?.(); + } + } else { + // Create new cron + const result = await createCron( + agent.assistant_id, + agent.deploymentId, + data + ); + if (result) { + form.reset(); + onSuccess?.(); + } + } + } catch (error) { + console.error("Failed to save cron:", error); + } + }; + + return ( + +
+
+ + +
+ +
+ + +

+ Use cron expression format: minute hour day month weekday. + Examples: +

+
    +
  • "0 9 * * *" - Daily at 9 AM
  • +
  • "0 * * * *" - Every hour
  • +
  • "*/15 * * * *" - Every 15 minutes
  • +
  • "0 0 * * 0" - Weekly on Sunday at midnight
  • +
+

+ + Use cron expression builder → + +

+
+ +
+ +