From 926c8d591a3a384d0e0cc3f406787eaced6a9032 Mon Sep 17 00:00:00 2001
From: Teknium <127238744+teknium1@users.noreply.github.com>
Date: Thu, 13 Aug 2026 01:52:45 -0700
Subject: [PATCH] feat(desktop): cron + blueprint recipes in the sidebar nav
rail
Adds a 'Scheduled jobs' row to the sidebar's top nav (below Artifacts,
watch codicon, wired to the existing nav.cron keybind action) so the
cron overlay opens from the same rail as New session / Capabilities /
Messaging / Artifacts.
Inside the cron overlay, the list rail now also shows the Automation
Blueprint recipes below the jobs (same search box filters both).
Clicking a recipe opens the create dialog pre-seeded to that
blueprint's typed-slot form via a new optional blueprintKey on the
create EditorState. Catalog fetch reuses the ['cron-blueprints'] query
key, so no extra request.
i18n: sidebar.nav.cron added to en/zh/zh-hant/ja (ar already had it).
---
apps/desktop/src/app/chat/sidebar/index.tsx | 9 +++++
apps/desktop/src/app/cron/index.tsx | 45 +++++++++++++++++++--
apps/desktop/src/app/types.ts | 2 +-
apps/desktop/src/i18n/en.ts | 3 +-
apps/desktop/src/i18n/ja.ts | 3 +-
apps/desktop/src/i18n/zh-hant.ts | 3 +-
apps/desktop/src/i18n/zh.ts | 3 +-
7 files changed, 59 insertions(+), 9 deletions(-)
diff --git a/apps/desktop/src/app/chat/sidebar/index.tsx b/apps/desktop/src/app/chat/sidebar/index.tsx
index f1587dcc3dc80..2e83cd75f199f 100644
--- a/apps/desktop/src/app/chat/sidebar/index.tsx
+++ b/apps/desktop/src/app/chat/sidebar/index.tsx
@@ -127,6 +127,7 @@ import { $sidebarSessionRankIds } from '@/store/sidebar-sort'
import {
type AppView,
ARTIFACTS_ROUTE,
+ CRON_ROUTE,
MESSAGING_ROUTE,
SIDEBAR_NAV_AREA,
type SidebarNavContribution,
@@ -203,6 +204,13 @@ const SIDEBAR_NAV: SidebarNavItem[] = [
icon: props => ,
route: ARTIFACTS_ROUTE,
keybindActionId: 'nav.artifacts'
+ },
+ {
+ id: 'cron',
+ label: '',
+ icon: props => ,
+ route: CRON_ROUTE,
+ keybindActionId: 'nav.cron'
}
]
@@ -1405,6 +1413,7 @@ export function ChatSidebar({
(item.id === 'skills' && currentView === 'skills') ||
(item.id === 'messaging' && currentView === 'messaging') ||
(item.id === 'artifacts' && currentView === 'artifacts') ||
+ (item.id === 'cron' && currentView === 'cron') ||
// Contributed rows light up at their own route.
(Boolean(item.route) && pathname === item.route)
diff --git a/apps/desktop/src/app/cron/index.tsx b/apps/desktop/src/app/cron/index.tsx
index 9f89b0d923fb0..ca18aaa2db444 100644
--- a/apps/desktop/src/app/cron/index.tsx
+++ b/apps/desktop/src/app/cron/index.tsx
@@ -356,6 +356,23 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt
[jobs, query]
)
+ // Blueprint recipes render in the same list rail, below the jobs — clicking
+ // one opens the create dialog pre-seeded to that recipe. Same query key as
+ // the dialog's "Start from" dropdown, so the catalog is fetched once.
+ const blueprintsQuery = useQuery({
+ queryKey: ['cron-blueprints'],
+ queryFn: async () => (await getAutomationBlueprints()).blueprints
+ })
+
+ const visibleBlueprints = useMemo(() => {
+ const list = blueprintsQuery.data ?? []
+ const needle = query.trim().toLowerCase()
+
+ return needle
+ ? list.filter(item => `${item.title} ${item.description}`.toLowerCase().includes(needle))
+ : list
+ }, [blueprintsQuery.data, query])
+
// Detail always reflects a concrete job: the explicitly selected one, else the
// first visible row, so the right pane is never empty while jobs exist.
const selectedJob = useMemo(
@@ -480,7 +497,7 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt
{loading && jobs.length === 0 ? (
- ) : totalCount === 0 ? (
+ ) : totalCount === 0 && visibleBlueprints.length === 0 ? (
setEditor({ mode: 'create' })} size="sm">
@@ -521,6 +538,21 @@ export function CronView({ onClose, onOpenSession, setStatusbarItemGroup: _setSt
{c.emptyTitleSearch}
)}
setEditor({ mode: 'create' })} />
+ {visibleBlueprints.length > 0 && (
+ <>
+ {c.blueprints.tab}
+ {visibleBlueprints.map(item => (
+ setEditor({ blueprintKey: item.key, mode: 'create' })}
+ rowKey={`blueprint-${item.key}`}
+ title={item.title}
+ />
+ ))}
+ >
+ )}
{selectedJob ? (
@@ -919,10 +951,10 @@ function CronEditorDialog({
setDeliver(initial ? jobDeliver(initial) : DEFAULT_DELIVER)
setModelChoice(initial && jobModel(initial) ? `${jobProvider(initial)}:${jobModel(initial)}` : MODEL_DEFAULT_VALUE)
setSlotValues({})
- setTemplateChoice(CUSTOM_TEMPLATE)
+ setTemplateChoice(editor.mode === 'create' ? (editor.blueprintKey ?? CUSTOM_TEMPLATE) : CUSTOM_TEMPLATE)
setError(null)
setSaving(false)
- }, [initial, open])
+ }, [editor, initial, open])
// Seed the typed slots with the blueprint's defaults whenever a blueprint is
// picked from "Start from" (and reset them when switching back to Custom).
@@ -1232,7 +1264,12 @@ function CronEditorDialog({
)
}
-type EditorState = { job: CronJob; mode: 'edit' } | { mode: 'closed' } | { mode: 'create' }
+type EditorState =
+ | { job: CronJob; mode: 'edit' }
+ | { mode: 'closed' }
+ // `blueprintKey` pre-selects a blueprint in the create dialog's "Start from"
+ // dropdown (set when a recipe row in the list rail is clicked).
+ | { blueprintKey?: string; mode: 'create' }
interface EditorValues {
deliver: string
diff --git a/apps/desktop/src/app/types.ts b/apps/desktop/src/app/types.ts
index 743c2b95be019..fe9398d7bbf02 100644
--- a/apps/desktop/src/app/types.ts
+++ b/apps/desktop/src/app/types.ts
@@ -158,7 +158,7 @@ export type CommandDispatchResponse =
| SendCommandDispatchResponse
| PrefillCommandDispatchResponse
-export type SidebarNavId = 'artifacts' | 'command-center' | 'messaging' | 'new-session' | 'settings' | 'skills'
+export type SidebarNavId = 'artifacts' | 'command-center' | 'cron' | 'messaging' | 'new-session' | 'settings' | 'skills'
export interface SidebarNavItem {
/** Built-in view id, or a contributed row's namespaced contribution id. */
diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts
index 3160533d69d1a..35916ae65cc6c 100644
--- a/apps/desktop/src/i18n/en.ts
+++ b/apps/desktop/src/i18n/en.ts
@@ -1865,7 +1865,8 @@ export const en: Translations = {
'new-session': 'New session',
skills: 'Capabilities',
messaging: 'Messaging',
- artifacts: 'Artifacts'
+ artifacts: 'Artifacts',
+ cron: 'Scheduled jobs'
},
searchAria: 'Search sessions',
searchPlaceholder: 'Search sessions…',
diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts
index 6ff8e056ac5e5..a4e49cc8f6948 100644
--- a/apps/desktop/src/i18n/ja.ts
+++ b/apps/desktop/src/i18n/ja.ts
@@ -1689,7 +1689,8 @@ export const ja = defineLocale({
'new-session': '新しいセッション',
skills: 'スキルとツール',
messaging: 'メッセージング',
- artifacts: 'アーティファクト'
+ artifacts: 'アーティファクト',
+ cron: 'スケジュール済みジョブ'
},
searchAria: 'セッションを検索',
searchPlaceholder: 'セッションを検索…',
diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts
index 9e1bfad1cc0c5..1020be810d1dd 100644
--- a/apps/desktop/src/i18n/zh-hant.ts
+++ b/apps/desktop/src/i18n/zh-hant.ts
@@ -1634,7 +1634,8 @@ export const zhHant = defineLocale({
'new-session': '新工作階段',
skills: '技能與工具',
messaging: '訊息平台',
- artifacts: '成品'
+ artifacts: '成品',
+ cron: '排程工作'
},
searchAria: '搜尋工作階段',
searchPlaceholder: '搜尋工作階段…',
diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts
index b6063d1cb45fb..471fa8767a324 100644
--- a/apps/desktop/src/i18n/zh.ts
+++ b/apps/desktop/src/i18n/zh.ts
@@ -2056,7 +2056,8 @@ export const zh: Translations = {
'new-session': '新建会话',
skills: '技能与工具',
messaging: '消息平台',
- artifacts: '产物'
+ artifacts: '产物',
+ cron: '定时任务'
},
searchAria: '搜索会话',
searchPlaceholder: '搜索会话…',