--- phase: 23-brainstormer-flow plan: 01 type: execute wave: 1 depends_on: ["23-00"] files_modified: - server/src/services/chat.ts - server/src/routes/chat.ts autonomous: true requirements: - AGENT-03 - AGENT-06 - AGENT-07 - CHAT-09 must_haves: truths: - "POST /conversations/:id/handoff inserts a handoff system message and creates issues" - "POST /conversations/:id/status-update inserts a status_update system message" - "addMessage accepts optional messageType and persists it" - "addSystemMessage helper creates system role messages with messageType" artifacts: - path: "server/src/services/chat.ts" provides: "addSystemMessage helper and messageType support in addMessage" contains: "addSystemMessage" - path: "server/src/routes/chat.ts" provides: "handoff and status-update routes" contains: "handoff" key_links: - from: "server/src/routes/chat.ts" to: "server/src/services/chat.ts" via: "svc.addSystemMessage call" pattern: "svc\\.addSystemMessage" - from: "server/src/routes/chat.ts" to: "server/src/routes/issues.ts" via: "issueService for task creation from handoff" pattern: "issueSvc\\.create" --- Server-side: extend chat service with addSystemMessage helper, messageType support in addMessage, and add handoff + status-update routes. Purpose: The handoff route is the backbone of the Brainstormer-to-PM flow. The status-update route enables agent completion notifications in chat. Both insert typed system messages. Output: Extended chat service, two new routes on chatRoutes. @.claude/get-shit-done/workflows/execute-plan.md @.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/23-brainstormer-flow/23-RESEARCH.md @.planning/phases/23-brainstormer-flow/23-00-SUMMARY.md From server/src/services/chat.ts: ```typescript export function chatService(db: Db) { return { async addMessage(conversationId: string, data: { role: string; content: string; agentId?: string }) { ... }, async getConversation(id: string) { ... }, // returns full row including companyId async editMessage(messageId: string, content: string) { ... }, // ... other methods }; } ``` From server/src/routes/chat.ts: ```typescript export function chatRoutes(db: Db): Router { const router = Router(); const svc = chatService(db); // Routes: GET/POST conversations, GET/PATCH/DELETE conversations/:id, // GET/POST messages, POST stream, PATCH messages/:msgId, DELETE messages/after/:msgId } ``` From packages/shared/src/validators/chat.ts (after Plan 00): ```typescript export const handoffSchema = z.object({ spec: z.object({ what: z.string().min(1), why: z.string().min(1), constraints: z.string().optional().default(""), success: z.string().optional().default("") }), targetRole: z.enum(["pm", "engineer", "general"]), }); ``` From packages/shared/src/validators/issue.ts: ```typescript export const createIssueSchema = z.object({ title: z.string().min(1), description: z.string().optional().nullable(), status: z.enum(ISSUE_STATUSES).optional().default("backlog"), priority: z.enum(ISSUE_PRIORITIES).optional().default("medium"), // ... many optional fields }); ``` Issue creation pattern: `issueService(db).create(companyId, { title, description, ... })` returns `{ id, identifier, title, ... }`. From server/src/routes/issues.ts (line 964): ```typescript router.post("/companies/:companyId/issues", validate(createIssueSchema), async (req, res) => { const issue = await svc.create(companyId, { ...req.body, createdByAgentId: actor.agentId, createdByUserId: ... }); res.status(201).json(issue); }); ``` The chatRoutes function currently receives only `db: Db`. To call issueService, either: (a) Import and instantiate issueService inside chatRoutes, or (b) Add issueService as a parameter to chatRoutes. Option (a) is simplest and matches how heartbeat.ts instantiates issueService locally. Task 1: Extend chat service with messageType support and addSystemMessage - server/src/services/chat.ts - packages/db/src/schema/chat_messages.ts server/src/services/chat.ts 1. Extend `addMessage` to accept optional `messageType?: string` in its data parameter. Pass `messageType: data.messageType ?? null` in the `.values()` call to `chatMessages`. This requires the schema from Plan 00 to include the messageType column. 2. Add `addSystemMessage` helper method to the returned service object: ```typescript async addSystemMessage( conversationId: string, data: { content: string; messageType: string; agentId?: string }, ) { const [message] = await db .insert(chatMessages) .values({ conversationId, role: "system", content: data.content, agentId: data.agentId ?? null, messageType: data.messageType, }) .returning(); // Bump conversation updatedAt (same pattern as addMessage Pitfall 3) await db .update(chatConversations) .set({ updatedAt: new Date() }) .where(eq(chatConversations.id, conversationId)); return message!; }, ``` 3. Also extend `listMessages` return — the `messageType` field will automatically be included in `select()` results since the Drizzle schema now defines it. No change needed in listMessages itself, but verify the return rows will include `messageType`. cd /opt/nexus && pnpm exec tsc --noEmit -p server/tsconfig.json 2>&1 | head -20 - grep -q "addSystemMessage" server/src/services/chat.ts - grep -q "messageType" server/src/services/chat.ts addMessage accepts messageType; addSystemMessage creates system messages with typed messageType; both bump conversation updatedAt Task 2: Add handoff and status-update routes - server/src/routes/chat.ts - server/src/services/issues.ts (first 20 lines + the create method signature) - server/src/routes/issues.ts (lines 964-1001 for create pattern) - packages/shared/src/validators/chat.ts server/src/routes/chat.ts 1. Import `handoffSchema` from `@paperclipai/shared` at the top of chat.ts. 2. Import `issueService` from `../services/issues.js` at the top. 3. Inside `chatRoutes(db)`, instantiate: `const issueSvc = issueService(db);` 4. Add `POST /conversations/:id/handoff` route (before the `return router` line): ```typescript router.post("/conversations/:id/handoff", async (req, res) => { assertBoard(req); const data = handoffSchema.parse(req.body); // Resolve companyId from conversation (Pitfall 4) const conversation = await svc.getConversation(req.params.id!); const companyId = conversation.companyId; // 1. Insert handoff system message const handoffMsg = await svc.addSystemMessage(req.params.id!, { content: `Brainstormer \u2192 PM: spec handed off`, messageType: "handoff", }); // 2. Create issue from spec const specDescription = [ `**What:** ${data.spec.what}`, `**Why:** ${data.spec.why}`, data.spec.constraints ? `**Constraints:** ${data.spec.constraints}` : "", data.spec.success ? `**Success:** ${data.spec.success}` : "", ].filter(Boolean).join("\n\n"); const issue = await issueSvc.create(companyId, { title: data.spec.what.slice(0, 100), description: specDescription, status: "backlog", priority: "medium", }); // 3. Insert task_created system message await svc.addSystemMessage(req.params.id!, { content: JSON.stringify({ taskId: issue.identifier, taskTitle: issue.title, taskUrl: `/issues/${issue.id}`, }), messageType: "task_created", }); res.json({ handoffMessageId: handoffMsg.id, issues: [issue] }); }); ``` 5. Add `POST /conversations/:id/status-update` route: ```typescript router.post("/conversations/:id/status-update", async (req, res) => { assertBoard(req); const { agentName, taskId, taskTitle, taskUrl } = req.body; if (!agentName || !taskId) { res.status(400).json({ error: "agentName and taskId are required" }); return; } const message = await svc.addSystemMessage(req.params.id!, { content: JSON.stringify({ agentName, taskId, taskTitle, taskUrl }), messageType: "status_update", }); res.status(201).json(message); }); ``` IMPORTANT: The `issueService` import path uses `.js` extension (ESM convention in this codebase). Check the existing imports in chat.ts and issues.ts for the exact pattern. The server uses `"../services/issues.js"` style imports. cd /opt/nexus && pnpm exec tsc --noEmit -p server/tsconfig.json 2>&1 | head -20 - grep -q "handoff" server/src/routes/chat.ts - grep -q "status-update" server/src/routes/chat.ts - grep -q "issueService" server/src/routes/chat.ts - grep -q "handoffSchema" server/src/routes/chat.ts - grep -q "addSystemMessage" server/src/routes/chat.ts POST /conversations/:id/handoff creates handoff message + issue + task_created message; POST /conversations/:id/status-update creates status_update message; both routes use assertBoard for auth - `pnpm exec tsc --noEmit -p server/tsconfig.json` passes - `pnpm vitest run --project=server` passes (existing tests not broken) - addSystemMessage helper exists in chat service - addMessage accepts optional messageType - Handoff route creates handoff + task_created system messages and an issue - Status-update route creates status_update system message - TypeScript compilation clean After completion, create `.planning/phases/23-brainstormer-flow/23-01-SUMMARY.md`