From 86a19129593cd30ab7ca7b73c70f82007dbb9224 Mon Sep 17 00:00:00 2001 From: Lukas May Date: Tue, 3 Mar 2026 13:40:37 +0100 Subject: [PATCH] feat: Add description field and auto-spawn discuss agent on initiative creation --- apps/server/trpc/routers/initiative.ts | 32 ++++++++++++++++++- .../src/components/CreateInitiativeDialog.tsx | 24 +++++++++++++- docs/frontend.md | 6 ++-- docs/server-api.md | 2 +- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/apps/server/trpc/routers/initiative.ts b/apps/server/trpc/routers/initiative.ts index 7b3ce4d..05583c4 100644 --- a/apps/server/trpc/routers/initiative.ts +++ b/apps/server/trpc/routers/initiative.ts @@ -5,14 +5,16 @@ import { TRPCError } from '@trpc/server'; import { z } from 'zod'; import type { ProcedureBuilder } from '../trpc.js'; -import { requireInitiativeRepository, requireProjectRepository, requireTaskRepository } from './_helpers.js'; +import { requireAgentManager, requireInitiativeRepository, requireProjectRepository, requireTaskRepository } from './_helpers.js'; import { deriveInitiativeActivity } from './initiative-activity.js'; +import { buildDiscussPrompt } from '../../agent/prompts/index.js'; export function initiativeProcedures(publicProcedure: ProcedureBuilder) { return { createInitiative: publicProcedure .input(z.object({ name: z.string().min(1), + description: z.string().optional(), branch: z.string().nullable().optional(), projectIds: z.array(z.string().min(1)).min(1).optional(), executionMode: z.enum(['yolo', 'review_per_phase']).optional(), @@ -55,6 +57,34 @@ export function initiativeProcedures(publicProcedure: ProcedureBuilder) { }); } + // Auto-spawn discuss agent when description is provided + if (input.description?.trim() && ctx.agentManager && ctx.taskRepository) { + try { + const taskRepo = requireTaskRepository(ctx); + const agentManager = requireAgentManager(ctx); + + const task = await taskRepo.create({ + initiativeId: initiative.id, + name: `Discuss: ${initiative.name}`, + description: input.description.trim(), + category: 'discuss', + status: 'in_progress', + }); + + const prompt = buildDiscussPrompt(); + + agentManager.spawn({ + taskId: task.id, + prompt, + mode: 'discuss', + initiativeId: initiative.id, + inputContext: { initiative }, + }); + } catch { + // Fire-and-forget — don't fail initiative creation if agent spawn fails + } + } + return initiative; }), diff --git a/apps/web/src/components/CreateInitiativeDialog.tsx b/apps/web/src/components/CreateInitiativeDialog.tsx index 021c7ab..c0ec131 100644 --- a/apps/web/src/components/CreateInitiativeDialog.tsx +++ b/apps/web/src/components/CreateInitiativeDialog.tsx @@ -1,4 +1,5 @@ import { useEffect, useState } from "react"; +import { useNavigate } from "@tanstack/react-router"; import { Dialog, DialogContent, @@ -10,6 +11,7 @@ import { 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 { Select, SelectContent, @@ -31,11 +33,13 @@ export function CreateInitiativeDialog({ onOpenChange, }: CreateInitiativeDialogProps) { const [name, setName] = useState(""); + const [description, setDescription] = useState(""); const [branch, setBranch] = useState(""); const [projectIds, setProjectIds] = useState([]); const [executionMode, setExecutionMode] = useState<"yolo" | "review_per_phase">("review_per_phase"); const [error, setError] = useState(null); + const navigate = useNavigate(); const utils = trpc.useUtils(); const createMutation = trpc.createInitiative.useMutation({ @@ -55,9 +59,10 @@ export function CreateInitiativeDialog({ utils.listInitiatives.setData(undefined, (old = []) => [tempInitiative, ...old]); return { previousInitiatives }; }, - onSuccess: () => { + onSuccess: (data) => { onOpenChange(false); toast.success("Initiative created"); + navigate({ to: "/initiatives/$id", params: { id: data.id } }); }, onError: (err, _variables, context) => { if (context?.previousInitiatives) { @@ -72,6 +77,7 @@ export function CreateInitiativeDialog({ useEffect(() => { if (open) { setName(""); + setDescription(""); setBranch(""); setProjectIds([]); setExecutionMode("review_per_phase"); @@ -84,6 +90,7 @@ export function CreateInitiativeDialog({ setError(null); createMutation.mutate({ name: name.trim(), + description: description.trim() || undefined, branch: branch.trim() || null, projectIds: projectIds.length > 0 ? projectIds : undefined, executionMode, @@ -112,6 +119,21 @@ export function CreateInitiativeDialog({ autoFocus /> +
+ +