feat: Add description field and auto-spawn discuss agent on initiative creation

This commit is contained in:
Lukas May
2026-03-03 13:40:37 +01:00
parent 9edc93a268
commit 86a1912959
4 changed files with 58 additions and 6 deletions

View File

@@ -5,14 +5,16 @@
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { z } from 'zod'; import { z } from 'zod';
import type { ProcedureBuilder } from '../trpc.js'; 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 { deriveInitiativeActivity } from './initiative-activity.js';
import { buildDiscussPrompt } from '../../agent/prompts/index.js';
export function initiativeProcedures(publicProcedure: ProcedureBuilder) { export function initiativeProcedures(publicProcedure: ProcedureBuilder) {
return { return {
createInitiative: publicProcedure createInitiative: publicProcedure
.input(z.object({ .input(z.object({
name: z.string().min(1), name: z.string().min(1),
description: z.string().optional(),
branch: z.string().nullable().optional(), branch: z.string().nullable().optional(),
projectIds: z.array(z.string().min(1)).min(1).optional(), projectIds: z.array(z.string().min(1)).min(1).optional(),
executionMode: z.enum(['yolo', 'review_per_phase']).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; return initiative;
}), }),

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate } from "@tanstack/react-router";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@@ -10,6 +11,7 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -31,11 +33,13 @@ export function CreateInitiativeDialog({
onOpenChange, onOpenChange,
}: CreateInitiativeDialogProps) { }: CreateInitiativeDialogProps) {
const [name, setName] = useState(""); const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [branch, setBranch] = useState(""); const [branch, setBranch] = useState("");
const [projectIds, setProjectIds] = useState<string[]>([]); const [projectIds, setProjectIds] = useState<string[]>([]);
const [executionMode, setExecutionMode] = useState<"yolo" | "review_per_phase">("review_per_phase"); const [executionMode, setExecutionMode] = useState<"yolo" | "review_per_phase">("review_per_phase");
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();
const utils = trpc.useUtils(); const utils = trpc.useUtils();
const createMutation = trpc.createInitiative.useMutation({ const createMutation = trpc.createInitiative.useMutation({
@@ -55,9 +59,10 @@ export function CreateInitiativeDialog({
utils.listInitiatives.setData(undefined, (old = []) => [tempInitiative, ...old]); utils.listInitiatives.setData(undefined, (old = []) => [tempInitiative, ...old]);
return { previousInitiatives }; return { previousInitiatives };
}, },
onSuccess: () => { onSuccess: (data) => {
onOpenChange(false); onOpenChange(false);
toast.success("Initiative created"); toast.success("Initiative created");
navigate({ to: "/initiatives/$id", params: { id: data.id } });
}, },
onError: (err, _variables, context) => { onError: (err, _variables, context) => {
if (context?.previousInitiatives) { if (context?.previousInitiatives) {
@@ -72,6 +77,7 @@ export function CreateInitiativeDialog({
useEffect(() => { useEffect(() => {
if (open) { if (open) {
setName(""); setName("");
setDescription("");
setBranch(""); setBranch("");
setProjectIds([]); setProjectIds([]);
setExecutionMode("review_per_phase"); setExecutionMode("review_per_phase");
@@ -84,6 +90,7 @@ export function CreateInitiativeDialog({
setError(null); setError(null);
createMutation.mutate({ createMutation.mutate({
name: name.trim(), name: name.trim(),
description: description.trim() || undefined,
branch: branch.trim() || null, branch: branch.trim() || null,
projectIds: projectIds.length > 0 ? projectIds : undefined, projectIds: projectIds.length > 0 ? projectIds : undefined,
executionMode, executionMode,
@@ -112,6 +119,21 @@ export function CreateInitiativeDialog({
autoFocus autoFocus
/> />
</div> </div>
<div className="space-y-2">
<Label htmlFor="initiative-description">
Description{" "}
<span className="text-muted-foreground font-normal">
(optional spawns a discuss agent)
</span>
</Label>
<Textarea
id="initiative-description"
placeholder="Describe what needs to be done..."
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={3}
/>
</div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="initiative-branch"> <Label htmlFor="initiative-branch">
Branch{" "} Branch{" "}

View File

@@ -140,9 +140,9 @@ Configured in `src/lib/trpc.ts`. Uses `@trpc/react-query` with TanStack Query fo
## Key User Flows ## Key User Flows
### Creating an Initiative ### Creating an Initiative
1. Dashboard → "New Initiative" → enter name, select projects 1. Dashboard → "New Initiative" → enter name, optional description, select projects
2. `createInitiative` mutation → auto-creates root page 2. `createInitiative` mutation → auto-creates root page; if description provided, auto-spawns discuss agent
3. Navigate to initiative detail 3. Navigate to initiative detail page on success
### Managing Content (Pages) ### Managing Content (Pages)
1. Content tab → page tree sidebar 1. Content tab → page tree sidebar

View File

@@ -84,7 +84,7 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE
### Initiatives ### Initiatives
| Procedure | Type | Description | | Procedure | Type | Description |
|-----------|------|-------------| |-----------|------|-------------|
| createInitiative | mutation | Create with optional branch/projectIds, auto-creates root page | | createInitiative | mutation | Create with optional branch/projectIds/description, auto-creates root page; if description provided, auto-spawns discuss agent |
| listInitiatives | query | Filter by status; returns `activity` (state, activePhase, phase counts) computed from phases | | listInitiatives | query | Filter by status; returns `activity` (state, activePhase, phase counts) computed from phases |
| getInitiative | query | With projects array | | getInitiative | query | With projects array |
| updateInitiative | mutation | Name, status | | updateInitiative | mutation | Name, status |