diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 29e6c26e1..508fe5861 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -181,13 +181,11 @@ function transformCronJob(job: GatewayCronJob) { // Extract message from payload const message = job.payload?.message || job.payload?.text || ''; - // Build target from delivery info - const channelType = job.delivery?.channel || 'unknown'; - const target = { - channelType, - channelId: channelType, - channelName: channelType, - }; + // Build target from delivery info — only if a delivery channel is specified + const channelType = job.delivery?.channel; + const target = channelType + ? { channelType, channelId: channelType, channelName: channelType } + : undefined; // Build lastRun from state const lastRun = job.state?.lastRunAtMs @@ -241,21 +239,16 @@ function registerCronHandlers(gatewayManager: GatewayManager): void { }); // Create a new cron job + // UI-created tasks have no delivery target — results go to the ClawX chat page. + // Tasks created via external channels (Feishu, Discord, etc.) are handled + // directly by the OpenClaw Gateway and do not pass through this IPC handler. ipcMain.handle('cron:create', async (_, input: { name: string; message: string; schedule: string; - target: { channelType: string; channelId: string; channelName: string }; enabled?: boolean; }) => { try { - // Transform frontend input to Gateway cron.add format - // For Discord, the recipient must be prefixed with "channel:" or "user:" - const recipientId = input.target.channelId; - const deliveryTo = input.target.channelType === 'discord' && recipientId - ? `channel:${recipientId}` - : recipientId; - const gatewayInput = { name: input.name, schedule: { kind: 'cron', expr: input.schedule }, @@ -263,11 +256,6 @@ function registerCronHandlers(gatewayManager: GatewayManager): void { enabled: input.enabled ?? true, wakeMode: 'next-heartbeat', sessionTarget: 'isolated', - delivery: { - mode: 'announce', - channel: input.target.channelType, - to: deliveryTo, - }, }; const result = await gatewayManager.rpc('cron.add', gatewayInput); // Transform the returned job to frontend format diff --git a/src/pages/Cron/index.tsx b/src/pages/Cron/index.tsx index 983be5262..d2604ed59 100644 --- a/src/pages/Cron/index.tsx +++ b/src/pages/Cron/index.tsx @@ -29,13 +29,12 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { useCronStore } from '@/stores/cron'; -import { useChannelsStore } from '@/stores/channels'; import { useGatewayStore } from '@/stores/gateway'; import { LoadingSpinner } from '@/components/common/LoadingSpinner'; import { formatRelativeTime, cn } from '@/lib/utils'; import { toast } from 'sonner'; import type { CronJob, CronJobCreateInput, ScheduleType } from '@/types/cron'; -import { CHANNEL_ICONS } from '@/types/channel'; +import { CHANNEL_ICONS, type ChannelType } from '@/types/channel'; import { useTranslation } from 'react-i18next'; // Common cron schedule presets @@ -123,7 +122,6 @@ interface TaskDialogProps { function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { const { t } = useTranslation('cron'); - const { channels } = useChannelsStore(); const [saving, setSaving] = useState(false); const [name, setName] = useState(job?.name || ''); @@ -141,13 +139,8 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { const [schedule, setSchedule] = useState(initialSchedule); const [customSchedule, setCustomSchedule] = useState(''); const [useCustom, setUseCustom] = useState(false); - const [channelId, setChannelId] = useState(job?.target.channelId || ''); - const [discordChannelId, setDiscordChannelId] = useState(''); const [enabled, setEnabled] = useState(job?.enabled ?? true); - const selectedChannel = channels.find((c) => c.id === channelId); - const isDiscord = selectedChannel?.type === 'discord'; - const handleSubmit = async () => { if (!name.trim()) { toast.error(t('toast.nameRequired')); @@ -157,15 +150,6 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { toast.error(t('toast.messageRequired')); return; } - if (!channelId) { - toast.error(t('toast.channelRequired')); - return; - } - // Validate Discord channel ID when Discord is selected - if (selectedChannel?.type === 'discord' && !discordChannelId.trim()) { - toast.error(t('toast.discordIdRequired')); - return; - } const finalSchedule = useCustom ? customSchedule : schedule; if (!finalSchedule.trim()) { @@ -175,26 +159,12 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { setSaving(true); try { - // For Discord, use the manually entered channel ID; for others, use empty - const actualChannelId = selectedChannel!.type === 'discord' - ? discordChannelId.trim() - : ''; - - await onSave( - // ... (args omitted from replacement content, ensuring they match target if not changed, but here I am replacing the block) - // Wait, I should not replace the whole onSave call if I don't need to. - // Let's target the toast. - { - name: name.trim(), - message: message.trim(), - schedule: finalSchedule, - target: { - channelType: selectedChannel!.type, - channelId: actualChannelId, - channelName: selectedChannel!.name, - }, - enabled, - }); + await onSave({ + name: name.trim(), + message: message.trim(), + schedule: finalSchedule, + enabled, + }); onClose(); toast.success(job ? t('toast.updated') : t('toast.created')); } catch (err) { @@ -285,46 +255,6 @@ function TaskDialog({ job, onClose, onSave }: TaskDialogProps) { - {/* Target Channel */} -
- {t('dialog.noChannels')} -
- ) : ( -- {t('dialog.discordChannelIdDesc')} -
-