Plan 00 (Wave 0): DB migration for message_type, shared types/validators, test stubs Plan 01 (Wave 1): Server — addSystemMessage, handoff route, status-update route Plan 02 (Wave 1): UI — ChatSpecCard, ChatHandoffIndicator, ChatTaskCreatedBadge, ChatStatusUpdateBadge, useBrainstormerDefault Plan 03 (Wave 2): Wiring — ChatMessage dispatch, ChatMessageList propagation, ChatPanel brainstormer default, chatApi handoff Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
285 lines
10 KiB
Markdown
285 lines
10 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@.claude/get-shit-done/workflows/execute-plan.md
|
|
@.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.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
|
|
|
|
<interfaces>
|
|
<!-- Key server interfaces the executor needs -->
|
|
|
|
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.
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Extend chat service with messageType support and addSystemMessage</name>
|
|
<read_first>
|
|
- server/src/services/chat.ts
|
|
- packages/db/src/schema/chat_messages.ts
|
|
</read_first>
|
|
<files>server/src/services/chat.ts</files>
|
|
<action>
|
|
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`.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm exec tsc --noEmit -p server/tsconfig.json 2>&1 | head -20</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- grep -q "addSystemMessage" server/src/services/chat.ts
|
|
- grep -q "messageType" server/src/services/chat.ts
|
|
</acceptance_criteria>
|
|
<done>addMessage accepts messageType; addSystemMessage creates system messages with typed messageType; both bump conversation updatedAt</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Add handoff and status-update routes</name>
|
|
<read_first>
|
|
- 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
|
|
</read_first>
|
|
<files>server/src/routes/chat.ts</files>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && pnpm exec tsc --noEmit -p server/tsconfig.json 2>&1 | head -20</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- 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
|
|
</acceptance_criteria>
|
|
<done>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</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `pnpm exec tsc --noEmit -p server/tsconfig.json` passes
|
|
- `pnpm vitest run --project=server` passes (existing tests not broken)
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- 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
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/23-brainstormer-flow/23-01-SUMMARY.md`
|
|
</output>
|