Files
Sybil-2/server/prisma/schema.prisma
T
buzzert ef3d79c428 Add persistent agent messaging and shared chat bubbles
Connect Sybil to the Pi root conversation with authenticated REST and WebSocket
APIs, persistent read state, and a dedicated agent tab. Keep optimistic messages
stable through delivery and retries, and animate arrivals without losing the
reader's scroll position.

Share bubble styling with regular chats and remove the obsolete admission sender.
Reject malformed upstream receipts and WebSocket upgrade requests, with regression
coverage and updated API documentation.

Validated with 51 web tests, 44 server tests, both production builds, desktop/mobile
bubble comparisons, and 11 browser animation and scrolling checks.
2026-10-14 12:20:11 -07:00

226 lines
4.6 KiB
Plaintext

// Prisma schema for the personal chat DB + LLM call log
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
enum Provider {
openai
anthropic
xai
gemini
hermes_agent @map("hermes-agent")
}
enum MessageRole {
system
user
assistant
tool
}
enum SearchSource {
exa
}
enum ProjectKind {
starred
folder
}
model User {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// minimal for now (single-user is fine). Keep extensible.
handle String? @unique
chats Chat[]
searches Search[]
projects Project[]
}
model Chat {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String?
titleGenerationPending Boolean @default(false)
initiatedProvider Provider?
initiatedModel String?
lastUsedProvider Provider?
lastUsedModel String?
additionalSystemPrompt String?
enabledTools Json?
user User? @relation(fields: [userId], references: [id])
userId String?
// Forks always point directly to the single root chat, never to another fork.
parentChat Chat? @relation("ChatForks", fields: [parentChatId], references: [id], onDelete: Cascade)
parentChatId String?
childChats Chat[] @relation("ChatForks")
messages Message[]
calls LlmCall[]
projectItems ProjectItem[]
@@index([userId])
@@index([parentChatId])
@@index([updatedAt])
}
model Message {
id String @id @default(cuid())
createdAt DateTime @default(now())
chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)
chatId String
role MessageRole
content String
// for tool messages or attachments later
name String?
metadata Json?
@@index([chatId, createdAt])
}
model LlmCall {
id String @id @default(cuid())
createdAt DateTime @default(now())
chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)
chatId String
provider Provider
model String
// request/response snapshots for debugging + replay
request Json
response Json?
// usage/cost basics
inputTokens Int?
outputTokens Int?
totalTokens Int?
latencyMs Int?
error String?
@@index([chatId, createdAt])
@@index([provider, model, createdAt])
}
model Search {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String?
query String?
queryNormalized String?
source SearchSource @default(exa)
requestId String?
rawResponse Json?
latencyMs Int?
error String?
answerText String?
answerRequestId String?
answerCitations Json?
answerRawResponse Json?
answerError String?
user User? @relation(fields: [userId], references: [id])
userId String?
results SearchResult[]
projectItems ProjectItem[]
@@index([updatedAt])
@@index([queryNormalized, updatedAt])
@@index([userId])
}
model SearchResult {
id String @id @default(cuid())
createdAt DateTime @default(now())
search Search @relation(fields: [searchId], references: [id], onDelete: Cascade)
searchId String
rank Int
title String?
url String
publishedDate String?
author String?
text String?
highlights Json?
highlightScores Json?
score Float?
favicon String?
image String?
@@index([searchId, rank])
}
model Project {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
kind ProjectKind @default(folder)
title String
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String?
items ProjectItem[]
@@index([kind])
@@index([userId])
}
model ProjectItem {
id String @id @default(cuid())
createdAt DateTime @default(now())
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
projectId String
chat Chat? @relation(fields: [chatId], references: [id], onDelete: Cascade)
chatId String?
search Search? @relation(fields: [searchId], references: [id], onDelete: Cascade)
searchId String?
@@unique([projectId, chatId])
@@unique([projectId, searchId])
@@index([projectId, createdAt])
@@index([chatId])
@@index([searchId])
}
// Shared personal read position for the external persistent Pi room.
// Messages remain in Pi's durable transcript, not duplicated in Sybil Chat.
model AgentReadState {
id String @id
cursor String @default("0")
updatedAt DateTime @updatedAt
}