diff --git a/AGENTS.md b/AGENTS.md index bb3e21e..00a99ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Veridian AGENTS.md -**Nuxt 4 AI chat platform with Triplit database + better-auth** +**Nuxt 4 AI chat platform with Drizzle + PostgreSQL + better-auth** ## Architecture Overview @@ -21,9 +21,10 @@ server/ # Server-side API routes ├── chat/ # Generation, cancel endpoints └── provider/ # Model fetching from providers -triplit/ # Database -├── schema.ts # Full schema with auth + app collections -└── auth-schema.ts # Auth collections (users, sessions, accounts) +drizzle/ # Database (migrated from Triplit) +├── schema.ts # Full schema with auth + app tables +├── relations.ts # Table relationships +└── migrations/ # SQL migration files lib/ # Shared utilities ├── auth.ts # Better-auth server config @@ -39,7 +40,9 @@ database commands. ### Database Commands -- `bunx triplit schema push` - Push schema changes to database +- `bunx drizzle-kit push` - Push schema changes to database +- `bunx drizzle-kit generate` - Generate migration from schema changes +- `bunx drizzle-kit migrate` - Run pending migrations ## Code Style @@ -57,15 +60,12 @@ database commands. Group imports in this order (alphabetical within groups): 1. Type imports (`types/`) 2. Dependency imports (npm packages) -3. Triplit imports (`#triplit/`, `@triplit/`) -4. Vue/Nuxt imports (`vue`, `#app`, `~~/`, `~/`, `@/`) -5. Local imports (`lib/`, `server/`) +3. Vue/Nuxt imports (`vue`, `#app`, `~~/`, `~/`, `@/`) +4. Local imports (`lib/`, `server/`) ```typescript // Example import order import type { Result, Ok, Err } from '~~/types/result'; -import type { Entity } from '@triplit/client'; -import type schema from '#triplit/schema'; import type { Foo } from 'vue'; import { ref, computed } from 'vue'; import { useFeature } from '~/composables/useFeature'; @@ -128,23 +128,28 @@ export default defineEventHandler(async (event) => { }); ``` -## Database Query Pattern (Triplit) +## Database Query Pattern (Drizzle) ```typescript -// Client queries use useQuery() with auto-includes -useQuery('collection', triplit, triplit.query('collection').Include('relation')); +// Server-side queries use Drizzle ORM +import { db } from '~~/server/db'; +import { agents } from '~~/drizzle/schema'; -// Server-side -import { httpClient } from '~~/server/lib/triplit'; -await httpClient.fetchOne(httpClient.query('collection').Where('id', '=', providerId)); +// Query with relations +const result = await db.query.agents.findMany({ + where: eq(agents.userId, userId), + with: { topics: true, messages: true }, +}); + +// Insert with conflict handling +await db.insert(agents).values(data).onConflictDoNothing(); ``` ## Authentication Flow 1. **Login**: Client uses `authClient.signIn.email()` → derives encryption key from password 2. **Session**: Better-auth creates session token → stored in cookie -3. **Triplit**: Session token passed to `triplit.startSession(token)` for DB access -4. **API Keys**: Encrypted client-side with AES-GCM (key derived from password + userId) +3. **API Keys**: Encrypted client-side with AES-GCM (key derived from password + userId) **Key files**: `lib/auth.ts`, `lib/auth-client.ts`, `app/plugins/auth.*.ts`, `app/middleware/auth.global.ts` @@ -159,10 +164,8 @@ await httpClient.fetchOne(httpClient.query('collection').Where('id', '=', provid | Variable | Purpose | |----------|---------| -| `TRIPLIT_SERVICE_TOKEN` | Admin DB access | -| `NUXT_TRIPLIT_ANON_TOKEN` | Anonymous DB access | +| `DATABASE_URL` | PostgreSQL connection string | | `BETTER_AUTH_SECRET` | Auth encryption | -| `NUXT_PUBLIC_TRIPLIT_URL` | DB server URL | ## Key Composables @@ -198,15 +201,40 @@ await httpClient.fetchOne(httpClient.query('collection').Where('id', '=', provid ## Known Issues (from BUGS.md) -1. Triplit occasionally makes duplicate connections (race condition) -2. Sidebar hover animation occasionally glitches on agent routes -3. Theme switcher + sidebar interaction bug +1. Sidebar hover animation occasionally glitches on agent routes +2. Theme switcher + sidebar interaction bug + +## Critical Architecture Notes + +### Chat Endpoint God File +- `server/api/topic/[topicId]/chat/index.post.ts` is 1263 lines handling route validation, tool definitions, streaming orchestration, DB transactions, and event emission +- Files that change together: this file + `server/utils/events.ts` + `server/utils/generations.ts` +- Tool definitions (bash, python, file access) should be extracted to separate module before extending + +### Security: Code Execution Tools Have No Sandboxing +- `bashTool` uses `exec()` and `pythonTool` uses `python3 -c` with user-controlled input — full RCE risk +- Do NOT extend these tools without adding: timeouts, path restrictions, resource limits +- Location: `server/api/topic/[topicId]/chat/index.post.ts` ~lines 261-455 + +### Crash Risk: `todo()` Function +- `todo()` literally throws `new Error('TODO')` on unhandled token types in chat handler +- Handle gracefully before production deployment +- Location: `server/api/topic/[topicId]/chat/index.post.ts` ~line 256 + +### Module-Scoped Mutable State Limitations +- `useDialog.ts` has module-scoped `actionCallback` — not SSR-safe, overwritten on concurrent dialogs +- `pendingGenerations` in `server/utils/generations.ts` and in-memory event channels in `server/utils/events.ts` won't work in multi-process deployments +- Fine for single-instance dev, but architectural ceiling for scaling + +### Pagination Params Validated But Unused +- `agents.get.ts` validates `page`/`limit` query params but never applies them to the query +- All agent queries return everything regardless of pagination params ## Development Notes - **Never run dev server as agent** - just complete tasks and signal done -- Always run `bunx triplit schema push` after schema changes +- Always run `bunx drizzle-kit push` after schema changes - Use `protectRoute()` in all API routes for auth - All UI state that persists → use cookies (`useCookie()`) -- Real-time data → Triplit subscriptions via `useQuery()` +- Real-time data → SSE events via `server/utils/events.ts` - API keys → encrypted client-side before DB storage diff --git a/app/app.vue b/app/app.vue index 5bbadc2..1c44b80 100644 --- a/app/app.vue +++ b/app/app.vue @@ -2,10 +2,11 @@ import '~/assets/css/reset.css'; import '~/assets/css/base.css'; -const { accent, neutral, hinting } = useUserSettings(); - -// by default, disable hinting -if (Number.isNaN(Number(hinting.value))) hinting.value = '0'; +const { user } = useAuth(); +const { accent, neutral, hinting, refresh: refreshSettings } = await useUserSettings(); +watch(user, () => { + refreshSettings(); +}) watchEffect(() => { useHead({ diff --git a/app/components/Attachment/Display.vue b/app/components/Attachment/Display.vue index 8a8da56..9fa5d8d 100644 --- a/app/components/Attachment/Display.vue +++ b/app/components/Attachment/Display.vue @@ -32,7 +32,7 @@ const fileExtension = computed(() => { diff --git a/app/components/Attachment/Preview.vue b/app/components/Attachment/Preview.vue index 1b11de8..b2902fb 100644 --- a/app/components/Attachment/Preview.vue +++ b/app/components/Attachment/Preview.vue @@ -21,7 +21,7 @@ const handleDelete = async () => { \ No newline at end of file diff --git a/app/pages/agent/[id]/topic/[topicId].vue b/app/pages/agent/[id]/topic/[topicId].vue index 22429b7..f99e4c4 100644 --- a/app/pages/agent/[id]/topic/[topicId].vue +++ b/app/pages/agent/[id]/topic/[topicId].vue @@ -1,131 +1,22 @@