diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index 9f7233b0..c7192143 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -1606,6 +1606,7 @@ export function setupIpcHandlers() { name: args.name, instructions: args.instructions, ...(args.triggers ? { triggers: args.triggers } : {}), + ...(args.projectId ? { projectId: args.projectId } : {}), ...(args.model ? { model: args.model } : {}), ...(args.provider ? { provider: args.provider } : {}), }); diff --git a/apps/x/apps/renderer/src/components/bg-tasks-view.tsx b/apps/x/apps/renderer/src/components/bg-tasks-view.tsx index 0641bc13..c48df79e 100644 --- a/apps/x/apps/renderer/src/components/bg-tasks-view.tsx +++ b/apps/x/apps/renderer/src/components/bg-tasks-view.tsx @@ -4,6 +4,7 @@ import { ListChecks, Play, Square, Loader2, Trash2, Plus, X, AlertCircle, Repeat, Clock, Zap, ChevronLeft, ChevronDown, ChevronRight, Pencil, Check, PanelRightClose, PanelRightOpen, Sparkles, + Code2, FolderOpen, LayoutTemplate, } from 'lucide-react' import type { z } from 'zod' import type { BackgroundTask, BackgroundTaskSummary, Triggers } from '@x/shared/dist/background-task.js' @@ -271,7 +272,16 @@ function TriggersEditor({ // New Task dialog // --------------------------------------------------------------------------- -type DialogMode = 'describe' | 'manual' +type DialogMode = 'describe' | 'manual' | 'templates' | 'coding' + +// Prefills for the "Coding from meetings" preset. +const CODING_PRESET = { + name: 'Implement coding items from meetings', + instructions: `After a meeting's notes are ready, scan them for coding action items (bugs to fix, features to build, concrete changes requested) for me or my team. + +Conservatively implement the clearly-scoped, self-contained ones in the configured repo using the launch-code-task tool — group related items into one session, split unrelated ones. Note ambiguous, large/architectural, or other-repo items as "needs review" instead of coding them. If nothing is actionable, do nothing.`, + eventMatchCriteria: `A meeting's notes or transcript just became available (engineering standup, planning, sprint, or technical discussion) that may contain coding action items, bugs to fix, or features to build.`, +} function NewTaskDialog({ open, @@ -295,6 +305,9 @@ function NewTaskDialog({ const [name, setName] = useState('') const [instructions, setInstructions] = useState('') const [triggers, setTriggers] = useState(undefined) + const [projectId, setProjectId] = useState(undefined) + const [projectName, setProjectName] = useState(undefined) + const [addingProject, setAddingProject] = useState(false) const [submitting, setSubmitting] = useState(false) useEffect(() => { @@ -304,11 +317,64 @@ function NewTaskDialog({ setName('') setInstructions('') setTriggers(undefined) + setProjectId(undefined) + setProjectName(undefined) } }, [open, copilotEnabled]) + // Switch into the coding preset: prefill name/instructions/trigger once. + const enterCodingMode = () => { + setMode('coding') + setName(CODING_PRESET.name) + setInstructions(CODING_PRESET.instructions) + setTriggers({ eventMatchCriteria: CODING_PRESET.eventMatchCriteria }) + } + + const pickRepo = async () => { + setAddingProject(true) + try { + const res = await window.ipc.invoke('dialog:openDirectory', { title: 'Choose the repository for this task' }) + const dir = res.path + if (!dir) return + const added = await window.ipc.invoke('codeProject:add', { path: dir }) + if (!added.git?.isGitRepo) { + toast('That folder is not a git repository — coding tasks need one.', 'error') + return + } + setProjectId(added.project.id) + setProjectName(added.project.name) + } catch (err) { + toast(err instanceof Error ? err.message : String(err), 'error') + } finally { + setAddingProject(false) + } + } + const canSubmitDescribe = description.trim().length > 0 && !submitting const canSubmitManual = name.trim().length > 0 && instructions.trim().length > 0 && !submitting + const canSubmitCoding = name.trim().length > 0 && instructions.trim().length > 0 && !!projectId && !submitting + + const submitCoding = async () => { + if (!canSubmitCoding) return + setSubmitting(true) + try { + const result = await window.ipc.invoke('bg-task:create', { + name: name.trim(), + instructions: instructions.trim(), + ...(triggers ? { triggers } : {}), + ...(projectId ? { projectId } : {}), + }) + if (result.success && result.slug) { + onCreated(result.slug) + } else { + toast(result.error ?? 'Failed to create task', 'error') + } + } catch (err) { + toast(err instanceof Error ? err.message : String(err), 'error') + } finally { + setSubmitting(false) + } + } const submitDescribe = () => { if (!canSubmitDescribe || !onCreateWithCopilot) return @@ -359,7 +425,116 @@ function NewTaskDialog({ - {mode === 'describe' ? ( + {(mode === 'describe' || mode === 'manual') && ( + + )} + + {mode === 'templates' ? ( + <> +
+ {[ + { + id: 'coding-from-meetings', + title: 'Coding from meetings', + description: "When a meeting's notes are ready, scan them for coding action items and auto-implement them in a repo — each on its own isolated branch, with a summary.", + icon: Code2, + onSelect: enterCodingMode, + }, + ].map(preset => ( + + ))} +
+ +
+ + +
+ + ) : mode === 'coding' ? ( + <> +
+
+ + {projectName ? ( +
+ + + {projectName} + + +
+ ) : ( + + )} +

+ Code changes run full-auto in an isolated git worktree — your working checkout is never touched. +

+
+
+ + setName(e.target.value)} /> +
+
+ +