# Multi-Agent Chat Interface - Cursor Rules ## Project Overview This is a Next.js 15 application deployed to Cloudflare Workers that provides a chat interface for multiple AI agents. Users can select from configured agents, chat with them through n8n webhooks, and create custom agents using Morgan (Agent Architect) with the Agent Forge feature. ## Core Architecture ### Tech Stack - **Framework**: Next.js 15.5.4 (App Router, Server Components + Client Components) - **React**: 19.1.0 with concurrent features - **Deployment**: Cloudflare Workers via OpenNext - **Styling**: Tailwind CSS 4.x with glass-morphism design system - **Animation**: Framer Motion - **State**: localStorage for client-side persistence (messages, sessions, pinned agents) - **Backend**: n8n webhooks for agent message processing - **Testing**: Vitest 4.0 + Testing Library ### Key Patterns - All page/component files use `"use client"` directive (Client Components) - Mobile-first responsive design with specific mobile breakpoint classes - No database - all state in localStorage and n8n workflows - Environment variables for agent configuration and feature flags - TypeScript with strict typing throughout ## Development Commands ```bash # Development pnpm dev # Start dev server at localhost:3000 pnpm build # Build Next.js application pnpm lint # Run ESLint pnpm test # Run Vitest tests pnpm test:ui # Vitest UI dashboard pnpm test:coverage # Generate coverage report # Deployment to Cloudflare pnpm build # Standard Next.js production build pnpm lint # Fix lint errors before packaging npx @opennextjs/cloudflare build # REQUIRED before deploy npx wrangler deploy # Deploy to Cloudflare npx wrangler tail # View live logs ``` **CRITICAL**: Always run the full deployment sequence above—`pnpm build`, `pnpm lint`, and `npx @opennextjs/cloudflare build`—before deploying. Skipping any step can push stale or broken assets. ## Agent Configuration ### Environment Variables Pattern ```env # Agent configuration (N = 1, 2, 3...) AGENT_N_URL=https://n8n.example.com/webhook/agent-N AGENT_N_NAME=Agent Display Name AGENT_N_DESCRIPTION=Agent description # Custom agents (Agent Forge) CUSTOM_AGENT_WEBHOOK=https://n8n.example.com/webhook/custom-agent CUSTOM_AGENT_REGISTRATION_WEBHOOK=https://n8n.example.com/webhook/register-agent # Feature flags IMAGE_UPLOADS_ENABLED=true DIFF_TOOL_ENABLED=true VOICE_INPUT_ENABLED=false ``` ### Agent Discovery - `/api/agents` dynamically discovers agents by iterating numbered environment variables - Returns array of `Agent` objects with id, name, description - Agent selection persisted to localStorage with key `selected-agent` ## Message Flow Architecture ### Standard Chat Flow 1. User submits message in `ChatInterface` component 2. POST `/api/chat` with: `{ message, agentId, sessionId, timestamp, images?: [] }` 3. Route extracts webhook URL from env vars based on agentId 4. Message forwarded to n8n webhook 5. Response parsed (supports streaming, tool calls, regular JSON) 6. Messages stored in localStorage: `chat-messages-{agentId}` ### n8n Response Format n8n workflows should output in this format (via Code node): ```json [{ "output": { "messageType": "regular_message" | "tool_call", "content": "Message text (always present)", "toolCall": { // Only for tool_call type "type": "tool_call", "name": "create_agent_package" | "show_diff", "payload": { /* tool-specific data */ } } } }] ``` ### Tool Call Handling - `create_agent_package`: Triggers `AgentForgeCard` component to display agent creation UI - `show_diff`: Renders side-by-side diff visualization via `DiffDisplay` component - Tool calls intercepted in `/api/chat` route and returned with `toolCall` field ## Agent Forge Feature ### Morgan (Agent Architect) - Agent ID: `agent-2` (typically) - System prompt: `.fortura-core/web-agents/agent-architect-web.md` - Creates custom agents that users can pin or use immediately - Outputs tool calls with complete agent prompt packages ### Custom Agent Creation Flow 1. User asks Morgan to create an agent 2. Morgan outputs `messageType: "tool_call"` with `create_agent_package` 3. Client displays `AgentForgeCard` with animated reveal 4. User actions: - **Use now**: Registers agent, switches to it immediately - **Pin for later**: Saves to localStorage `pinned-agents` array - **Share**: Copies agent info to clipboard ### Custom Agent Storage ```typescript // localStorage["pinned-agents"] { agentId: "custom-{uuid}", displayName: "Agent Name", summary: "Description", tags: ["tag1", "tag2"], systemPrompt: "Full prompt text", recommendedIcon: "🔮", whenToUse: "...", pinnedAt: "ISO timestamp", note: "User note" } ``` ## File Structure & Conventions ### API Routes (`src/app/api/`) - `agents/route.ts` - Discovers agents from env vars - `agents/create/route.ts` - Registers custom agents with n8n - `chat/route.ts` - Proxies messages to agent webhooks, handles tool calls - `flags/route.ts` - Returns feature flags ### Components (`src/components/`) - `chat-interface.tsx` - Main chat UI with message history, composer, agent selection - `markdown-renderer.tsx` - Renders markdown with syntax highlighting, custom blocks - `agent-forge-card.tsx` - Displays agent creation with animated reveal - `pinned-agents-drawer.tsx` - Sliding drawer for pinned agent management - `diff-tool.tsx` / `diff-display.tsx` - Side-by-side diff visualization ### Type Definitions (`src/lib/types.ts`) All TypeScript interfaces for Agent, Message, ChatRequest, ChatResponse, ToolCall, AgentPackagePayload, PinnedAgent, CustomAgent ### Feature Flags (`src/lib/flags.ts`) - Server-side: `getFlags()` returns all flags - Client-side: `useFlags()` hook fetches from `/api/flags` - Environment variables: `IMAGE_UPLOADS_ENABLED`, `DIFF_TOOL_ENABLED`, `VOICE_INPUT_ENABLED` ## Styling System ### Glass-Morphism Design Custom CSS variables in Tailwind config: - `charcoal` - Dark backgrounds - `burnt-orange` / `terracotta` - Accent colors - Glass effects: `backdrop-blur`, semi-transparent backgrounds - Mobile classes: `mobile-shell`, `mobile-feed`, `mobile-composer` ### Component Styling Patterns ```tsx // Glass card with shadow className="rounded-2xl border border-white/25 bg-white/15 shadow-[0_2px_6px_rgba(0,0,0,0.12)] backdrop-blur" // Button primary className="bg-gradient-to-r from-burnt-orange to-terracotta text-white" // Mobile responsive className="mobile-shell md:container" ``` ## LocalStorage Keys ```typescript // Agent selection "selected-agent" // Full Agent object "selected-agent-id" // String // Per-agent data "chat-session-{agentId}" // Session ID "chat-messages-{agentId}" // Message array // Custom agents "pinned-agents" // PinnedAgent[] ``` ## Testing Guidelines ### Test Structure - Place tests in `__tests__/` organized by domain - Use Vitest + Testing Library for React components - Mock `global.fetch` for API tests - Call `resetFlagsCache()` in `beforeEach` when testing flags ### Test Patterns ```typescript // API route test import { POST } from '@/app/api/chat/route' const request = new NextRequest('http://localhost/api/chat', { method: 'POST', body: JSON.stringify({ message: 'test', agentId: 'agent-1' }) }) const response = await POST(request) // Component test import { render, screen } from '@testing-library/react' render() expect(screen.getByText('Hello')).toBeInTheDocument() ``` ## Critical Rules ### DO - ✅ Use TypeScript strict mode - ✅ Add `"use client"` to interactive components - ✅ Handle both success and error paths - ✅ Validate environment variables exist - ✅ Use feature flags for experimental features - ✅ Persist state to localStorage appropriately - ✅ Test both mobile and desktop layouts - ✅ Clean up localStorage on session reset - ✅ Handle n8n response format variations - ✅ Log important state changes with `[v0]` prefix ### DON'T - ❌ Use Vercel-specific features (deployed to Cloudflare) - ❌ Store sensitive data in localStorage unencrypted - ❌ Assume agent webhook URLs exist without checking - ❌ Skip OpenNext build before Cloudflare deploy - ❌ Hardcode agent IDs (use dynamic discovery) - ❌ Modify git config or force push - ❌ Skip linting or tests - ❌ Commit without explicit user request - ❌ Use emojis unless user requests them - ❌ Create documentation files unless requested ## n8n Integration ### Webhook Request Format ```typescript { message: string sessionId: string agentId: string timestamp: string images?: string[] // base64 encoded } ``` ### Webhook Response Handling The `/api/chat` route handles multiple n8n formats: 1. Streaming chunks (newline-delimited JSON with `type: "item"`) 2. Tool calls (`type: "tool_call"`) 3. Code node output (`[{ output: { messageType, content, toolCall? } }]`) 4. Regular JSON with various fields (`response`, `message`, `output`, `text`) 5. Plain text fallback ### Custom Agent Workflow - **Custom Agent Webhook**: Handles messages for `custom-*` agent IDs - **Registration Webhook**: Stores agent prompts when user pins/uses agents - Routes check `agentId.startsWith("custom-")` to use `CUSTOM_AGENT_WEBHOOK` ## Morgan System Prompt Updates ### Location `.fortura-core/web-agents/agent-architect-web.md` ### JSON Output Format (CRITICAL) Morgan must output valid JSON matching this schema: ```json { "messageType": "regular_message" | "tool_call", "content": "Message text", "toolCall": { // Only when messageType is "tool_call" "type": "tool_call", "name": "create_agent_package", "payload": { "agentId": "custom-{uuid}", "displayName": "Agent Name", "summary": "Description", "tags": ["tag1", "tag2"], "systemPrompt": "Full prompt with Web Agent Bundle wrapper", "hints": { "recommendedIcon": "🔮", "whenToUse": "..." } } } } ``` ### n8n Code Node Setup Morgan's n8n workflow uses a Code node (NOT structured output parser) to ensure clean JSON: ```javascript const llmOutput = $input.item.json.output || $input.item.json; let actual = llmOutput; while (actual.output && typeof actual.output === 'object') { actual = actual.output; // Unwrap nested output } return { json: { output: actual } }; ``` ## Common Tasks ### Adding a New Agent 1. Add env vars: `AGENT_N_URL`, `AGENT_N_NAME`, `AGENT_N_DESCRIPTION` 2. Create n8n workflow with webhook trigger 3. Ensure n8n outputs in correct format (Code node wraps response) 4. Restart app - agent auto-discovered by `/api/agents` ### Adding a New Tool Call Type 1. Define payload interface in `src/lib/types.ts` 2. Add handler in `/api/chat` route to detect tool call 3. Create React component to render tool call 4. Import and conditionally render in `ChatInterface` ### Deploying to Cloudflare ```bash pnpm build pnpm lint npx @opennextjs/cloudflare build npx wrangler deploy ``` **Important:** Always use `npx wrangler deploy` without any `--env` flags. The deployment uses the default configuration from `wrangler.jsonc` with route `agents.nicholai.work`. Update `wrangler.jsonc` with new env vars in the top-level `vars` section. ### Deployment Checklist (must be followed exactly): 1. **Never use npm.** Always run commands with pnpm (e.g. `pnpm install`, `pnpm dev`, `pnpm build`). 2. **Run the standard Next.js build to catch compile issues early:** ```bash pnpm build ``` 3. **Fix lint errors before packaging for Cloudflare:** ```bash pnpm lint ``` Warnings are acceptable, but errors must be resolved before moving on. 4. **Before every deploy, run the Cloudflare build step:** ```bash npx @opennextjs/cloudflare build ``` If this fails, fix the error before proceeding—Wrangler deploys without this step will push stale assets. 5. **Deploy only after a successful OpenNext build using:** ```bash npx wrangler deploy ``` **Do NOT use `--env` flags.** The deployment uses the default configuration from `wrangler.jsonc`. **Troubleshooting:** - "No updated asset files to upload": you skipped the Cloudflare build; rerun step 2. - ESLint config errors during build: they're informational—build still succeeds, but you should address them separately. - Viewport metadata warning: move viewport values from metadata to generateViewport per Next.js docs. **No deploy is compliant unless each step above is completed and verified.** ## Debugging Tips ### Check Agent Configuration ```bash # Server logs show discovered agents grep "Found agent" logs # Test agent webhook directly curl -X POST $AGENT_1_URL -H "Content-Type: application/json" -d '{"message":"test"}' ``` ### Check n8n Response Format ```bash # Server logs show raw webhook response grep "\[v0\] Webhook response body" logs grep "\[v0\] Parsed webhook data" logs grep "\[v0\] parsedOutput messageType" logs ``` ### Check LocalStorage ```javascript // Browser console localStorage.getItem('pinned-agents') localStorage.getItem('chat-messages-agent-2') localStorage.getItem('selected-agent') ``` ## Performance Considerations - Message history stored per agent (not global) to reduce payload - Agent discovery cached server-side (env vars don't change at runtime) - Markdown rendering memoized - Framer Motion animations use GPU acceleration - Lazy load agent resources (don't load until switched) ## Security Notes - System prompts stored in plain text in localStorage (future: encryption) - No authentication (public chat interface) - Agent webhooks should validate requests - Environment variables should not be exposed to client - Feature flags fetched from server (not bundled in client) ## Future Enhancements (Not Yet Implemented) - Voice input (flag exists: `VOICE_INPUT_ENABLED`) - Image annotation - Server-side agent sync across devices - Agent marketplace/sharing - System prompt encryption - Cross-device session sync --- ## UI/UX Refinements (2025-11-15) ### Message Display - User messages limited to 75% max-width with proper text wrapping - Messages positioned on right but text is left-aligned (natural flow) - Code blocks in messages have overflow handling with horizontal scrolling - Inline code and block code properly contained within message bubbles ### Button Layout - "New chat" button positioned on left side of manuscript panel - Bookmark/pinned agents button positioned on right side - Buttons use `ml-auto` flex layout for proper spacing ### Scrollbar Styling - Changed from orange to neutral gray (`#9ca3af` light mode, `#6b7280` dark mode) - Scrollbars hidden by default, show on hover - Maintains consistency with minimalist design ### Pill Animations - Fixed flicker by initializing `pinnedAgents` from localStorage in state initializer - Added 0.2s base delay to all pills for staggered reveal effect - Each pill staggered 60ms apart for smooth cascade animation ### "Create New" Button - Enhanced animation with bouncy scale: `[1, 1.1, 0.95, 1.05, 1]` - Duration: 0.6s with `easeInOut` easing - Provides clear visual feedback before selecting Morgan and populating input ### Pinned Agents Drawer **Mobile (Bottom Sheet):** - Appears as full-width bottom sheet on mobile - Drag handle indicator at top - Rounded top corners with semi-transparent dark background - Smooth slide-up animation from bottom **Desktop (Side Drawer):** - Slides out from under the main glass manuscript card to the right - Positioned relative to manuscript panel, partially behind it - Shorter height (slightly less than manuscript panel) - Centered vertically on screen - 40% less opacity than desktop UI baseline for subtle appearance - Smooth slide animation with easeInOut timing **Card Features:** - Minimalist design showing only name, handle, and start chat button - Hover effects: description and button become visible on hover - Smooth height expansion animation on hover (grid-based) - Drag-and-drop reordering with visual feedback - Dragged cards render above all other cards (z-index management) - Delete button visible only on hover **Animation Details:** - Entrance: 0.2s opacity fade-in + scale from left - Exit: Slide deeper left with 0.28s opacity fade-out, easeIn timing - Card content transitions smoothly between collapsed/expanded states **Last Updated**: 2025-11-15 **Version**: 1.1