303 lines
12 KiB
Markdown
303 lines
12 KiB
Markdown
---
|
|
phase: 21-chat-foundation
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: ["21-00"]
|
|
files_modified:
|
|
- packages/db/src/schema/chat_conversations.ts
|
|
- packages/db/src/schema/chat_messages.ts
|
|
- packages/db/src/schema/index.ts
|
|
- packages/shared/src/types/chat.ts
|
|
- packages/shared/src/validators/chat.ts
|
|
- packages/shared/src/types/index.ts
|
|
- packages/shared/src/validators/index.ts
|
|
autonomous: true
|
|
requirements: [HIST-01, HIST-06]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Conversations and messages written to the database survive a server restart"
|
|
- "Shared types and Zod validators are importable from @paperclipai/shared"
|
|
- "A new migration SQL file exists that can create the chat tables on first server start"
|
|
artifacts:
|
|
- path: "packages/db/src/schema/chat_conversations.ts"
|
|
provides: "chatConversations Drizzle table"
|
|
exports: ["chatConversations"]
|
|
- path: "packages/db/src/schema/chat_messages.ts"
|
|
provides: "chatMessages Drizzle table"
|
|
exports: ["chatMessages"]
|
|
- path: "packages/shared/src/types/chat.ts"
|
|
provides: "ChatConversation and ChatMessage TypeScript types"
|
|
exports: ["ChatConversation", "ChatMessage", "ChatConversationListItem"]
|
|
- path: "packages/shared/src/validators/chat.ts"
|
|
provides: "Zod schemas for create/update operations"
|
|
exports: ["createConversationSchema", "updateConversationSchema", "createMessageSchema"]
|
|
key_links:
|
|
- from: "packages/db/src/schema/chat_conversations.ts"
|
|
to: "packages/db/src/schema/companies.ts"
|
|
via: "FK companyId references companies.id"
|
|
pattern: "references.*companies\\.id"
|
|
- from: "packages/db/src/schema/chat_messages.ts"
|
|
to: "packages/db/src/schema/chat_conversations.ts"
|
|
via: "FK conversationId references chatConversations.id with onDelete cascade"
|
|
pattern: "onDelete.*cascade"
|
|
- from: "packages/db/src/schema/index.ts"
|
|
to: "packages/db/src/schema/chat_conversations.ts"
|
|
via: "re-export"
|
|
pattern: "export.*chatConversations.*chat_conversations"
|
|
---
|
|
|
|
<objective>
|
|
Create the database schema and shared types for the chat system.
|
|
|
|
Purpose: Establish the persistence layer that all subsequent plans depend on — two new Drizzle tables (chat_conversations, chat_messages), a migration, and shared TypeScript types + Zod validators.
|
|
Output: Migration SQL applied, tables created, types and validators importable from @paperclipai/shared.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/21-chat-foundation/21-RESEARCH.md
|
|
|
|
<interfaces>
|
|
<!-- Existing schema pattern to follow (from documents.ts) -->
|
|
From packages/db/src/schema/documents.ts:
|
|
```typescript
|
|
import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
|
|
```
|
|
|
|
From packages/db/src/schema/companies.ts:
|
|
```typescript
|
|
export const companies = pgTable("companies", { id: uuid("id").primaryKey().defaultRandom(), ... });
|
|
```
|
|
|
|
From packages/db/src/schema/agents.ts:
|
|
```typescript
|
|
export const agents = pgTable("agents", { id: uuid("id").primaryKey().defaultRandom(), ... });
|
|
```
|
|
|
|
From packages/shared/src/types/index.ts — re-exports all type modules.
|
|
From packages/shared/src/validators/index.ts — re-exports all validator modules.
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Create Drizzle schema files and generate migration</name>
|
|
<files>packages/db/src/schema/chat_conversations.ts, packages/db/src/schema/chat_messages.ts, packages/db/src/schema/index.ts</files>
|
|
<read_first>
|
|
- packages/db/src/schema/documents.ts (reference pattern for pgTable, timestamps, indexes)
|
|
- packages/db/src/schema/companies.ts (FK target for companyId)
|
|
- packages/db/src/schema/agents.ts (FK target for agentId)
|
|
- packages/db/src/schema/index.ts (current re-exports to extend)
|
|
</read_first>
|
|
<action>
|
|
Create `packages/db/src/schema/chat_conversations.ts`:
|
|
```typescript
|
|
import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
|
|
import { companies } from "./companies.js";
|
|
import { agents } from "./agents.js";
|
|
|
|
export const chatConversations = pgTable(
|
|
"chat_conversations",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
companyId: uuid("company_id").notNull().references(() => companies.id),
|
|
title: text("title"),
|
|
agentId: uuid("agent_id").references(() => agents.id, { onDelete: "set null" }),
|
|
pinnedAt: timestamp("pinned_at", { withTimezone: true }),
|
|
archivedAt: timestamp("archived_at", { withTimezone: true }),
|
|
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("chat_conversations_company_updated_idx").on(table.companyId, table.updatedAt),
|
|
index("chat_conversations_company_deleted_idx").on(table.companyId, table.deletedAt),
|
|
],
|
|
);
|
|
```
|
|
|
|
Create `packages/db/src/schema/chat_messages.ts`:
|
|
```typescript
|
|
import { pgTable, uuid, text, timestamp, index } from "drizzle-orm/pg-core";
|
|
import { chatConversations } from "./chat_conversations.js";
|
|
|
|
export const chatMessages = pgTable(
|
|
"chat_messages",
|
|
{
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
conversationId: uuid("conversation_id").notNull()
|
|
.references(() => chatConversations.id, { onDelete: "cascade" }),
|
|
role: text("role").notNull(),
|
|
content: text("content").notNull(),
|
|
agentId: uuid("agent_id"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
},
|
|
(table) => [
|
|
index("chat_messages_conversation_created_idx").on(table.conversationId, table.createdAt),
|
|
],
|
|
);
|
|
```
|
|
|
|
Add to `packages/db/src/schema/index.ts` at the end:
|
|
```typescript
|
|
export { chatConversations } from "./chat_conversations.js";
|
|
export { chatMessages } from "./chat_messages.js";
|
|
```
|
|
|
|
IMPORTANT: Check the index helper pattern in the existing schema files — Drizzle v0.38 may use the array syntax `(table) => [index(...)]` instead of the object syntax `(table) => ({...})`. Match whichever pattern the existing `documents.ts` or `issues.ts` uses.
|
|
|
|
Then run:
|
|
```bash
|
|
cd /opt/nexus && pnpm db:generate
|
|
```
|
|
This generates the migration SQL file. Inspect it to confirm it contains `CREATE TABLE chat_conversations`, `CREATE TABLE chat_messages`, `ON DELETE CASCADE`, and the two indexes. Do NOT run `pnpm db:migrate` — that happens at server start.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && grep -r "chat_conversations" packages/db/src/schema/index.ts && grep -r "chat_messages" packages/db/src/schema/index.ts && ls packages/db/src/migrations/*.sql | tail -1 | xargs grep -l "chat_conversations"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- packages/db/src/schema/chat_conversations.ts contains `export const chatConversations = pgTable(`
|
|
- packages/db/src/schema/chat_messages.ts contains `export const chatMessages = pgTable(`
|
|
- packages/db/src/schema/chat_messages.ts contains `onDelete: "cascade"`
|
|
- packages/db/src/schema/index.ts contains `export { chatConversations } from "./chat_conversations.js"`
|
|
- packages/db/src/schema/index.ts contains `export { chatMessages } from "./chat_messages.js"`
|
|
- A migration SQL file exists in packages/db/src/migrations/ containing `CREATE TABLE "chat_conversations"`
|
|
- The migration SQL contains `ON DELETE CASCADE`
|
|
- The migration SQL contains `chat_conversations_company_updated_idx`
|
|
</acceptance_criteria>
|
|
<done>Both Drizzle schema files exist, are exported from index.ts, and a migration SQL has been generated containing the correct DDL with FK constraints and indexes.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Create shared types and Zod validators for chat</name>
|
|
<files>packages/shared/src/types/chat.ts, packages/shared/src/validators/chat.ts, packages/shared/src/types/index.ts, packages/shared/src/validators/index.ts</files>
|
|
<read_first>
|
|
- packages/shared/src/types/company.ts (reference pattern for type definitions)
|
|
- packages/shared/src/validators/company.ts (reference pattern for Zod schemas)
|
|
- packages/shared/src/types/index.ts (current re-exports)
|
|
- packages/shared/src/validators/index.ts (current re-exports)
|
|
</read_first>
|
|
<action>
|
|
Create `packages/shared/src/types/chat.ts`:
|
|
```typescript
|
|
export interface ChatConversation {
|
|
id: string;
|
|
companyId: string;
|
|
title: string | null;
|
|
agentId: string | null;
|
|
pinnedAt: string | null;
|
|
archivedAt: string | null;
|
|
deletedAt: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface ChatConversationListItem {
|
|
id: string;
|
|
companyId: string;
|
|
title: string | null;
|
|
agentId: string | null;
|
|
pinnedAt: string | null;
|
|
archivedAt: string | null;
|
|
updatedAt: string;
|
|
lastMessagePreview: string | null;
|
|
}
|
|
|
|
export interface ChatMessage {
|
|
id: string;
|
|
conversationId: string;
|
|
role: "user" | "assistant" | "system";
|
|
content: string;
|
|
agentId: string | null;
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface ChatConversationListResponse {
|
|
items: ChatConversationListItem[];
|
|
hasMore: boolean;
|
|
}
|
|
|
|
export interface ChatMessageListResponse {
|
|
items: ChatMessage[];
|
|
hasMore: boolean;
|
|
}
|
|
```
|
|
|
|
Create `packages/shared/src/validators/chat.ts`:
|
|
```typescript
|
|
import { z } from "zod";
|
|
|
|
export const createConversationSchema = z.object({
|
|
title: z.string().max(200).optional(),
|
|
agentId: z.string().uuid().optional(),
|
|
});
|
|
|
|
export const updateConversationSchema = z.object({
|
|
title: z.string().max(200).optional(),
|
|
agentId: z.string().uuid().nullable().optional(),
|
|
pinnedAt: z.string().datetime().nullable().optional(),
|
|
archivedAt: z.string().datetime().nullable().optional(),
|
|
});
|
|
|
|
export const createMessageSchema = z.object({
|
|
role: z.enum(["user", "assistant", "system"]),
|
|
content: z.string().min(1).max(100_000),
|
|
agentId: z.string().uuid().optional(),
|
|
});
|
|
```
|
|
|
|
Add to `packages/shared/src/types/index.ts`:
|
|
```typescript
|
|
export * from "./chat.js";
|
|
```
|
|
|
|
Add to `packages/shared/src/validators/index.ts`:
|
|
```typescript
|
|
export * from "./chat.js";
|
|
```
|
|
</action>
|
|
<verify>
|
|
<automated>cd /opt/nexus && npx tsx -e "import { createConversationSchema, createMessageSchema } from '@paperclipai/shared'; console.log('validators OK');" 2>/dev/null || grep -q "createConversationSchema" packages/shared/src/validators/chat.ts && grep -q "ChatConversation" packages/shared/src/types/chat.ts && echo "files OK"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- packages/shared/src/types/chat.ts contains `export interface ChatConversation`
|
|
- packages/shared/src/types/chat.ts contains `export interface ChatMessage`
|
|
- packages/shared/src/types/chat.ts contains `export interface ChatConversationListItem`
|
|
- packages/shared/src/validators/chat.ts contains `export const createConversationSchema`
|
|
- packages/shared/src/validators/chat.ts contains `export const updateConversationSchema`
|
|
- packages/shared/src/validators/chat.ts contains `export const createMessageSchema`
|
|
- packages/shared/src/types/index.ts contains `export * from "./chat.js"`
|
|
- packages/shared/src/validators/index.ts contains `export * from "./chat.js"`
|
|
</acceptance_criteria>
|
|
<done>Chat types (ChatConversation, ChatMessage, ChatConversationListItem) and Zod validators (createConversationSchema, updateConversationSchema, createMessageSchema) exist and are re-exported from the shared package barrel files.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- Migration SQL file contains CREATE TABLE for both chat_conversations and chat_messages
|
|
- Schema files follow existing Drizzle pattern (pgTable, uuid PKs, timestamp with timezone)
|
|
- chat_messages FK has ON DELETE CASCADE
|
|
- Types and validators importable from @paperclipai/shared
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Two new Drizzle schema files created and exported
|
|
- Migration SQL generated with correct DDL
|
|
- Shared types and Zod validators created and re-exported
|
|
- No modifications to existing tables
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/21-chat-foundation/21-01-SUMMARY.md`
|
|
</output>
|