feat: Add description field and auto-spawn discuss agent on initiative creation
This commit is contained in:
@@ -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;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -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{" "}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
Reference in New Issue
Block a user