diff --git a/.planning/phases/22-agent-streaming/22-01-PLAN.md b/.planning/phases/22-agent-streaming/22-01-PLAN.md index ab4f10a8..d02e421c 100644 --- a/.planning/phases/22-agent-streaming/22-01-PLAN.md +++ b/.planning/phases/22-agent-streaming/22-01-PLAN.md @@ -53,10 +53,17 @@ must_haves: --- -Server-side streaming infrastructure: DB schema additions for message editing, SSE streaming endpoint for LLM token delivery, message edit route, agent selection on conversations, and server tests. +Server-side streaming infrastructure: DB schema additions for message editing, SSE streaming endpoint with echo-stream placeholder, message edit route, agent selection on conversations, and server tests. Purpose: Establishes the entire server-side API surface that the UI plans (02/03) will consume. Every new endpoint is tested. -Output: Working SSE stream endpoint, edit message endpoint, conversation agent update, migration SQL, tests. +Output: Working SSE stream endpoint (echo-stream mode), edit message endpoint, conversation agent update, migration SQL, tests. + +NOTE -- Echo-stream scope (CHAT-01 partial): The SSE endpoint uses an echo-stream that replays the +user's last message word-by-word. This fully exercises the streaming pipeline (SSE headers, token +events, done event, abort detection, message persistence) so the UI can be built and tested against +real streaming behavior. Real LLM integration (replacing the echo loop with an adapter call) is +Phase 23 (Brainstormer agent). The echo-stream satisfies CHAT-01's "tokens appear as generated" +contract at the transport level; Phase 23 provides semantic content. @@ -177,30 +184,30 @@ res.write(":ok\n\n"); - Test: PUT /conversations/:id/messages/:messageId with { content: "new" } returns 200 with editedContent set - 1. **DB schema** — Add two columns to `chatMessages` in `packages/db/src/schema/chat_messages.ts`: + 1. **DB schema** -- Add two columns to `chatMessages` in `packages/db/src/schema/chat_messages.ts`: ```typescript editedContent: text("edited_content"), editedAt: timestamp("edited_at", { withTimezone: true }), ``` Then run `pnpm db:generate` to create the migration SQL. - 2. **Shared types** — Update `ChatMessage` interface in `packages/shared/src/types/chat.ts`: + 2. **Shared types** -- Update `ChatMessage` interface in `packages/shared/src/types/chat.ts`: - Add `editedContent: string | null;` - Add `editedAt: string | null;` - 3. **Validators** — In `packages/shared/src/validators/chat.ts`: + 3. **Validators** -- In `packages/shared/src/validators/chat.ts`: - Update `updateConversationSchema` to include `agentId: z.string().uuid().optional().nullable()` - Add `export const editMessageSchema = z.object({ content: z.string().min(1) });` - Add `export const streamMessageSchema = z.object({ content: z.string().min(1), agentId: z.string().uuid().optional().nullable() });` - 4. **Service methods** — Add to `chatService` in `server/src/services/chat.ts`: - - `editMessage(messageId: string, data: { content: string })` — sets `editedContent = data.content`, `editedAt = new Date()` on the message row, returns the updated row - - `getMessageHistory(conversationId: string)` — selects all messages WHERE conversationId matches, ORDER BY createdAt ASC (ascending, for LLM context window). Returns `ChatMessage[]`. Use `editedContent ?? content` as the effective content field (alias as `effectiveContent` in the return). + 4. **Service methods** -- Add to `chatService` in `server/src/services/chat.ts`: + - `editMessage(messageId: string, data: { content: string })` -- sets `editedContent = data.content`, `editedAt = new Date()` on the message row, returns the updated row + - `getMessageHistory(conversationId: string)` -- selects all messages WHERE conversationId matches, ORDER BY createdAt ASC (ascending, for LLM context window). Returns `ChatMessage[]`. Use `editedContent ?? content` as the effective content field (alias as `effectiveContent` in the return). - Update `updateConversation` to accept and persist `agentId` field: `set({ title: data.title, agentId: data.agentId, updatedAt: new Date() })`. Only set fields that are provided (check `data.agentId !== undefined` before including in set). 5. **Extend existing tests** in `server/src/__tests__/chat-routes.test.ts`: - - Add test: `PATCH /conversations/:id with agentId` — create conversation, PATCH with `{ agentId: someAgentId }`, verify response has the agentId set. (Use a dummy UUID string for agentId if the test DB doesn't enforce FK — check existing test patterns.) - - Add test: `PUT /conversations/:id/messages/:messageId` — create conversation, add message, PUT with `{ content: "edited" }`, verify response has `editedContent: "edited"` and `editedAt` is not null. + - Add test: `PATCH /conversations/:id with agentId` -- create conversation, PATCH with `{ agentId: someAgentId }`, verify response has the agentId set. (Use a dummy UUID string for agentId if the test DB doesn't enforce FK -- check existing test patterns.) + - Add test: `PUT /conversations/:id/messages/:messageId` -- create conversation, add message, PUT with `{ content: "edited" }`, verify response has `editedContent: "edited"` and `editedAt` is not null. pnpm --filter @paperclipai/server test run -- --reporter=verbose chat-routes @@ -221,7 +228,7 @@ res.write(":ok\n\n"); - Task 2: SSE streaming endpoint + edit message route + stream tests + Task 2: SSE echo-stream endpoint + edit message route + stream tests server/src/routes/chat.ts, server/src/__tests__/chat-stream-routes.test.ts @@ -241,7 +248,13 @@ res.write(":ok\n\n"); - Test: Client close (req.destroy()) stops the stream loop - 1. **Edit message route** — Add to `server/src/routes/chat.ts`: + **IMPORTANT -- Echo-stream placeholder:** This task implements an echo-stream (replays the user's + last message word-by-word) as a functional placeholder. This is intentional -- it fully exercises + the SSE pipeline so the UI (Plans 02/03) can develop against real streaming behavior. Phase 23 + will replace the echo loop body with real LLM adapter calls. The SSE contract (token events, + done event, abort detection, message persistence) is the deliverable here, not LLM content. + + 1. **Edit message route** -- Add to `server/src/routes/chat.ts`: ```typescript // PUT /conversations/:id/messages/:messageId router.put("/conversations/:id/messages/:messageId", validate(editMessageSchema), async (req, res) => { @@ -255,7 +268,7 @@ res.write(":ok\n\n"); }); ``` - 2. **SSE stream endpoint** — Add to `server/src/routes/chat.ts`: + 2. **SSE stream endpoint** -- Add to `server/src/routes/chat.ts`: ```typescript // GET /conversations/:id/stream router.get("/conversations/:id/stream", async (req, res) => { @@ -269,7 +282,7 @@ res.write(":ok\n\n"); return; } - // Set SSE headers — copied from plugins.ts:1146 + // Set SSE headers -- copied from plugins.ts:1146 res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", @@ -288,10 +301,11 @@ res.write(":ok\n\n"); // Get message history for LLM context const history = await svc.getMessageHistory(conversationId); - // For now: echo-stream mode. The actual LLM call will be wired when a provider - // is configured. This streams tokens from the last user message content one word - // at a time as a functional placeholder that fully exercises the SSE pipeline. - // Phase 23+ will replace this with real LLM calls via the agent's adapterConfig. + // ECHO-STREAM PLACEHOLDER (Phase 22): + // Streams the user's last message back word-by-word to fully exercise the SSE + // pipeline. Phase 23 replaces this block with: + // const adapter = resolveAdapter(agentId); + // for await (const token of adapter.stream(history)) { ... } const lastUserMsg = history.filter(m => m.role === "user").at(-1); const echoContent = lastUserMsg ? `Echo from agent: ${lastUserMsg.content}` @@ -325,9 +339,9 @@ res.write(":ok\n\n"); Import `editMessageSchema` and `streamMessageSchema` from `@paperclipai/shared` at the top of the routes file (alongside existing imports). - 3. **Stream tests** — Create `server/src/__tests__/chat-stream-routes.test.ts`: + 3. **Stream tests** -- Create `server/src/__tests__/chat-stream-routes.test.ts`: - Use the same test DB setup pattern as `chat-routes.test.ts` (read that file for the pattern). - - Test: `GET /conversations/:id/stream?triggerMessageId=X` — create conversation, add user message, open stream, collect all SSE data events, verify: + - Test: `GET /conversations/:id/stream?triggerMessageId=X` -- create conversation, add user message, open stream, collect all SSE data events, verify: - Response status is 200 - Content-Type header contains "text/event-stream" - X-Accel-Buffering header is "no" @@ -352,26 +366,27 @@ res.write(":ok\n\n"); - grep -q "flushHeaders" server/src/routes/chat.ts returns 0 - grep -q 'type: "done"' server/src/routes/chat.ts returns 0 - grep -q 'type: "token"' server/src/routes/chat.ts returns 0 + - grep -q "ECHO-STREAM PLACEHOLDER" server/src/routes/chat.ts returns 0 - test -f server/src/__tests__/chat-stream-routes.test.ts - pnpm --filter @paperclipai/server test run -- chat-stream exits 0 - pnpm --filter @paperclipai/server test run exits 0 (all server tests green) - SSE stream endpoint returns text/event-stream with token+done events, edit message route works, abort detection stops streaming, all server tests pass + SSE echo-stream endpoint returns text/event-stream with token+done events (placeholder for Phase 23 LLM integration), edit message route works, abort detection stops streaming, all server tests pass -- `pnpm --filter @paperclipai/server test run` — all server tests pass +- `pnpm --filter @paperclipai/server test run` -- all server tests pass - `pnpm db:generate` has been run and migration exists -- SSE endpoint tested with token + done events +- SSE endpoint tested with token + done events (echo-stream mode) - Edit message route tested with editedContent persistence - PATCH conversation with agentId tested 1. New migration SQL exists and applies the editedContent + editedAt columns -2. GET /conversations/:id/stream returns text/event-stream with token events then done event +2. GET /conversations/:id/stream returns text/event-stream with token events then done event (echo-stream placeholder -- Phase 23 replaces with real LLM) 3. PUT /conversations/:id/messages/:messageId updates editedContent and editedAt 4. PATCH /conversations/:id with { agentId } persists the agent selection 5. All server tests pass (both chat-routes and chat-stream-routes) diff --git a/.planning/phases/22-agent-streaming/22-02-PLAN.md b/.planning/phases/22-agent-streaming/22-02-PLAN.md index 7dc9610f..5a1e1241 100644 --- a/.planning/phases/22-agent-streaming/22-02-PLAN.md +++ b/.planning/phases/22-agent-streaming/22-02-PLAN.md @@ -36,6 +36,8 @@ must_haves: - path: "ui/src/components/AgentSelector.tsx" provides: "Dropdown to select active agent per conversation" exports: ["AgentSelector"] + - path: "ui/src/components/ChatInput.slash-mention.test.tsx" + provides: "Integration tests for slash command and @mention parsing in ChatInput context" key_links: - from: "ui/src/components/ChatAgentBadge.tsx" to: "ui/src/lib/agent-colors.ts" @@ -120,16 +122,19 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti - Task 1: Agent color utility + parseMessageIntent function + tests + Task 1: Agent color utility + parseMessageIntent function + tests (including slash/mention integration stubs) ui/src/lib/agent-colors.ts, ui/src/lib/parseMessageIntent.ts, - ui/src/lib/parseMessageIntent.test.ts + ui/src/lib/parseMessageIntent.test.ts, + ui/src/components/ChatInput.slash-mention.test.tsx ui/src/lib/agent-icons.ts, ui/src/lib/utils.ts, ui/src/index.css (search for --chart-1 through --chart-5 definitions), + ui/src/components/ChatInput.tsx, + ui/src/components/ChatInput.test.tsx, .planning/phases/22-agent-streaming/22-UI-SPEC.md (Color section, agent role colors table), .planning/phases/22-agent-streaming/22-RESEARCH.md (Pattern 4: slash command parsing, Pattern 6: agent colors) @@ -149,7 +154,7 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti - Test: parseMessageIntent("@PM-agent Check this") returns { text: "Check this", targetName: "pm-agent" } - Test: parseMessageIntent("/unknown-command Hello") returns { text: "/unknown-command Hello" } (no targetRole) - Test: parseMessageIntent("Just a normal message") returns { text: "Just a normal message" } - - Test: parseMessageIntent("/path/to/file.ts") returns { text: "/path/to/file.ts" } (no targetRole — not followed by space) + - Test: parseMessageIntent("/path/to/file.ts") returns { text: "/path/to/file.ts" } (no targetRole -- not followed by space) 1. **Create `ui/src/lib/agent-colors.ts`:** @@ -209,22 +214,32 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti ``` 3. **Create `ui/src/lib/parseMessageIntent.test.ts`:** Write Vitest tests covering all the behaviors listed above. Use `describe("parseMessageIntent", () => { ... })` and `describe("agentRoleColorClass", () => { ... })` blocks. Import from the respective modules. + + 4. **Create `ui/src/components/ChatInput.slash-mention.test.tsx`:** Create test stubs for INPUT-05 (slash command parsing in ChatInput context) and INPUT-06 (@mention parsing in ChatInput context). These test the integration between ChatInput and parseMessageIntent: + - Use the same jsdom + createRoot + act pattern as `ChatInput.test.tsx`. + - Test stub (INPUT-05): "slash command prefix filters SLASH_COMMANDS and shows popover" -- import `SLASH_COMMANDS`, verify the exported constant has entries for /brainstorm, /ask-pm, /ask-engineer, /task, /search. (Full popover rendering tests will be added in Plan 03 when the popover is wired into ChatInput.) + - Test stub (INPUT-06): "@mention prefix resolves agent name" -- import `parseMessageIntent`, verify `parseMessageIntent("@test-agent hello").targetName` equals "test-agent". (Full popover rendering tests added in Plan 03.) + - Mark any tests that depend on Plan 03 UI changes with `it.todo(...)` so they are tracked but do not block. - pnpm --filter @paperclipai/ui test run -- --reporter=verbose parseMessageIntent + pnpm --filter @paperclipai/ui test run -- --reporter=verbose parseMessageIntent ChatInput.slash - test -f ui/src/lib/agent-colors.ts - test -f ui/src/lib/parseMessageIntent.ts - test -f ui/src/lib/parseMessageIntent.test.ts + - test -f ui/src/components/ChatInput.slash-mention.test.tsx - grep -q "agentRoleColorClass" ui/src/lib/agent-colors.ts returns 0 - grep -q "parseMessageIntent" ui/src/lib/parseMessageIntent.ts returns 0 - grep -q "SLASH_COMMANDS" ui/src/lib/parseMessageIntent.ts returns 0 - grep -q "/brainstorm" ui/src/lib/parseMessageIntent.ts returns 0 - grep -q "@mention" ui/src/lib/parseMessageIntent.ts OR grep -q "targetName" returns 0 + - grep -q "INPUT-05" ui/src/components/ChatInput.slash-mention.test.tsx returns 0 + - grep -q "INPUT-06" ui/src/components/ChatInput.slash-mention.test.tsx returns 0 - pnpm --filter @paperclipai/ui test run -- parseMessageIntent exits 0 + - pnpm --filter @paperclipai/ui test run -- ChatInput.slash exits 0 - Agent color mapping utility and message intent parsing with slash commands + @mentions both implemented and fully tested + Agent color mapping utility and message intent parsing with slash commands + @mentions both implemented and fully tested. ChatInput slash/mention integration test stubs created for INPUT-05 and INPUT-06. @@ -253,14 +268,14 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti Layout per UI-SPEC: - Container: `flex items-center gap-2 mb-1` - Avatar circle: `w-5 h-5 rounded-full flex items-center justify-center` + `agentRoleColorClass(agent.role)` background + `text-white` - - If agent has an `icon` value: use the `AgentIcon` component at 12px (check how `agent-icons.ts` maps icon strings to lucide components — read that file). If no `AgentIcon` component exists, render the lucide `Bot` icon at 12px. + - If agent has an `icon` value: use the `AgentIcon` component at 12px (check how `agent-icons.ts` maps icon strings to lucide components -- read that file). If no `AgentIcon` component exists, render the lucide `Bot` icon at 12px. - If no icon: render first letter of `agent.name` at `text-[10px] font-semibold text-white` - Agent name: `` - Fallback (agent not found): `Bot` icon (12px) + "Agent" text, `bg-muted` background - Avatar element: `aria-hidden="true"` (decorative per accessibility contract) 2. **Create `ui/src/components/ChatAgentBadge.test.tsx`:** - Use jsdom + createRoot + act pattern (same as `ChatInput.test.tsx` — read that file for the testing pattern). NOT `@testing-library/react`. + Use jsdom + createRoot + act pattern (same as `ChatInput.test.tsx` -- read that file for the testing pattern). NOT `@testing-library/react`. Tests: - Renders agent name when agentId matches an agent in the array @@ -284,7 +299,7 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti - If `isLoading`: render `` - On value change: call `onSelect(value)` - No test file needed for AgentSelector (it's a thin UI wrapper over shadcn Select with no logic — the color mapping is tested via agent-colors, and the integration will be verified in Plan 03's checkpoint). + No test file needed for AgentSelector (it's a thin UI wrapper over shadcn Select with no logic -- the color mapping is tested via agent-colors, and the integration will be verified in Plan 03's checkpoint). pnpm --filter @paperclipai/ui test run -- --reporter=verbose ChatAgentBadge @@ -308,11 +323,12 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti -- `pnpm --filter @paperclipai/ui test run` — all UI tests pass -- `pnpm --filter @paperclipai/ui build` — TypeScript compiles +- `pnpm --filter @paperclipai/ui test run` -- all UI tests pass +- `pnpm --filter @paperclipai/ui build` -- TypeScript compiles - Agent color utility tested for all 5 roles + fallback - parseMessageIntent tested for all 5 slash commands + @mention + plain text + edge cases - ChatAgentBadge tested for render + fallback + accessibility +- ChatInput.slash-mention.test.tsx exists with INPUT-05 and INPUT-06 stubs @@ -320,7 +336,8 @@ Shadcn components already installed (per UI-SPEC): Avatar, Badge, Select, Toolti 2. parseMessageIntent correctly parses all 5 slash commands and @mention syntax 3. ChatAgentBadge renders agent name + colored avatar, with fallback for unknown agents 4. AgentSelector provides a dropdown with tooltip and empty state -5. All UI tests pass and build succeeds +5. ChatInput.slash-mention.test.tsx has test stubs for INPUT-05 and INPUT-06 +6. All UI tests pass and build succeeds diff --git a/.planning/phases/22-agent-streaming/22-03-PLAN.md b/.planning/phases/22-agent-streaming/22-03-PLAN.md index 86fe8dca..7cc210b3 100644 --- a/.planning/phases/22-agent-streaming/22-03-PLAN.md +++ b/.planning/phases/22-agent-streaming/22-03-PLAN.md @@ -202,7 +202,7 @@ import { VList } from "virtua"; pnpm --filter @paperclipai/ui add virtua ``` - 1. **Extend `ui/src/api/chat.ts`** — Add these methods to the `chatApi` object: + 1. **Extend `ui/src/api/chat.ts`** -- Add these methods to the `chatApi` object: ```typescript editMessage: (conversationId: string, messageId: string, data: { content: string }) => api.put(`/api/conversations/${conversationId}/messages/${messageId}`, data), @@ -210,7 +210,7 @@ import { VList } from "virtua"; api.patch(`/api/conversations/${id}`, { agentId }), ``` - 2. **Extend `ui/src/hooks/useChatMessages.ts`** — Add `useStreamMessage` hook: + 2. **Extend `ui/src/hooks/useChatMessages.ts`** -- Add `useStreamMessage` hook: ```typescript export function useStreamMessage(conversationId: string | null) { const queryClient = useQueryClient(); @@ -265,7 +265,7 @@ import { VList } from "virtua"; esRef.current = null; setStreaming(false); setPartialContent(""); - // Toast would go here — for now log + // Toast would go here -- for now log console.error("Stream error:", parsed.message); } } catch { @@ -283,7 +283,7 @@ import { VList } from "virtua"; const retry = useCallback(async (agentId?: string | null) => { if (!conversationId || streaming) return; - // Retry: open stream without posting a new message — server re-generates from last user message + // Retry: open stream without posting a new message -- server re-generates from last user message setStreaming(true); setPartialContent(""); @@ -342,7 +342,7 @@ import { VList } from "virtua"; Add required imports at top: `useState, useCallback, useRef, useEffect` from react. - 3. **Extend `ui/src/hooks/useChatConversations.ts`** — Add `useUpdateConversationAgent` hook: + 3. **Extend `ui/src/hooks/useChatConversations.ts`** -- Add `useUpdateConversationAgent` hook: ```typescript export function useUpdateConversationAgent() { const queryClient = useQueryClient(); @@ -359,7 +359,7 @@ import { VList } from "virtua"; Add import for `chatApi` (should already be imported; if not, add `import { chatApi } from "../api/chat";`). - pnpm --filter @paperclipai/ui build + pnpm --filter @paperclipai/ui build && pnpm --filter @paperclipai/ui test run - grep -q "virtua" ui/package.json returns 0 @@ -372,8 +372,9 @@ import { VList } from "virtua"; - grep -q "partialContent" ui/src/hooks/useChatMessages.ts returns 0 - grep -q "streaming" ui/src/hooks/useChatMessages.ts returns 0 - pnpm --filter @paperclipai/ui build exits 0 + - pnpm --filter @paperclipai/ui test run exits 0 - virtua installed, API client extended with editMessage + updateConversationAgent, useStreamMessage hook with EventSource streaming + stop + retry, useEditMessage mutation, useUpdateConversationAgent mutation, build passes + virtua installed, API client extended with editMessage + updateConversationAgent, useStreamMessage hook with EventSource streaming + stop + retry, useEditMessage mutation, useUpdateConversationAgent mutation, build and tests pass. Note: useStreamMessage is tested via the full integration in Plan 04's visual checkpoint rather than unit tests, since EventSource requires complex browser mocking -- the hook's logic is straightforward state management over a well-tested SSE endpoint. @@ -397,6 +398,11 @@ import { VList } from "virtua"; .planning/phases/22-agent-streaming/22-UI-SPEC.md (full Interaction Contract + Component Inventory) + NOTE: This task touches 3 tightly-coupled components that share streaming state. They are kept + in one task because splitting would create artificial seams -- ChatPanel owns the state that + ChatMessageList and ChatInput consume. Implement in order: A (ChatMessageList), B (ChatInput), + C (ChatPanel wiring). + **A. Rewrite `ChatMessageList.tsx`** to use virtua VList with agent badges and action buttons: Replace the entire component. New props interface: @@ -449,7 +455,7 @@ import { VList } from "virtua"; ``` 3. Track `isAtBottom` state: use VList's `onScroll` callback. Virtua's VList provides `onScroll` with the scroll offset. Calculate: `isAtBottom = (event.scrollOffset + event.viewportSize >= event.scrollSize - 80)`. Initialize `isAtBottom` to `true`. 4. Auto-scroll during streaming: when `streaming` is true and `isAtBottom`, after each partialContent change, call `listRef.current?.scrollToIndex(allMessages.length, { smooth: false })` via a useEffect. - 5. Keep `role="log"` and `aria-live="polite"` on an outer wrapper div (not the VList itself — VList is the scroll container). + 5. Keep `role="log"` and `aria-live="polite"` on an outer wrapper div (not the VList itself -- VList is the scroll container). **MessageItem** (inline component or extracted): ```tsx @@ -508,7 +514,7 @@ import { VList } from "virtua"; {message.editedAt && " (edited)"} - {/* Action buttons — visible on hover, hidden during streaming */} + {/* Action buttons -- visible on hover, hidden during streaming */} {!streaming && !editing && (
{message.role === "user" && ( @@ -544,7 +550,7 @@ import { VList } from "virtua"; Imports needed: `VList` from `virtua`, `ChatAgentBadge` from `./ChatAgentBadge`, `ChatMarkdownMessage` from `./ChatMarkdownMessage`, `Button` from `@/components/ui/button`, `Pencil, RotateCcw, ChevronDown` from `lucide-react`, `Agent` from `@paperclipai/shared`, `useState, useRef, useEffect, useCallback` from `react`, `cn` from `../lib/utils`. - **B. Update `ChatInput.tsx`** — Add Stop button, slash command popover, @mention popover: + **B. Update `ChatInput.tsx`** -- Add Stop button, slash command popover, @mention popover: New props interface: ```typescript @@ -603,9 +609,9 @@ import { VList } from "virtua"; - Render same `` + `` pattern - On item select: replace input with `@{agentName} `, close popover - The popover trigger is the textarea container itself (invisible trigger — use `` on the textarea wrapper div). + The popover trigger is the textarea container itself (invisible trigger -- use `` on the textarea wrapper div). - **C. Update `ChatPanel.tsx`** — Wire everything together: + **C. Update `ChatPanel.tsx`** -- Wire everything together: 1. Import `AgentSelector` from `./AgentSelector`. 2. Import `useStreamMessage, useEditMessage` from `../hooks/useChatMessages`. @@ -650,7 +656,7 @@ import { VList } from "virtua"; try { const conversation = await createConversation.mutateAsync(undefined); setActiveConversationId(conversation.id); - // Can't stream yet — conversation just created, need to wait for state update + // Can't stream yet -- conversation just created, need to wait for state update // Queue the send for after state settles setTimeout(() => stream.send(content, resolveAgentId(intent, agents, conversation.agentId)), 50); } catch { /* ignore */ } @@ -704,7 +710,7 @@ import { VList } from "virtua"; }, [editMessage, stream, activeConversation]); ``` - 16. Add `AgentSelector` to the panel header. Modify the inner layout — add a header bar above the message area: + 16. Add `AgentSelector` to the panel header. Modify the inner layout -- add a header bar above the message area: ```tsx {/* Message area */}
@@ -778,9 +784,9 @@ import { VList } from "virtua"; -- `pnpm --filter @paperclipai/ui build` — TypeScript compiles -- `pnpm --filter @paperclipai/ui test run` — all UI tests pass -- `pnpm test run` — full suite green +- `pnpm --filter @paperclipai/ui build` -- TypeScript compiles +- `pnpm --filter @paperclipai/ui test run` -- all UI tests pass +- `pnpm test run` -- full suite green - ChatMessageList uses VList from virtua - ChatInput shows Stop button during streaming - ChatPanel has AgentSelector in header diff --git a/.planning/phases/22-agent-streaming/22-04-PLAN.md b/.planning/phases/22-agent-streaming/22-04-PLAN.md index 45c5de6f..ea769450 100644 --- a/.planning/phases/22-agent-streaming/22-04-PLAN.md +++ b/.planning/phases/22-agent-streaming/22-04-PLAN.md @@ -10,9 +10,14 @@ requirements: [CHAT-01, CHAT-08, CHAT-10, CHAT-11, CHAT-12, INPUT-05, INPUT-06, must_haves: truths: - - "Full streaming chat flow works end-to-end" - - "All three themes render agent colors correctly" - - "All six success criteria from ROADMAP are met" + - "pnpm test run exits 0 with all chat-routes, chat-stream-routes, parseMessageIntent, ChatAgentBadge, and ChatInput.slash-mention tests passing" + - "pnpm --filter @paperclipai/ui build and pnpm --filter @paperclipai/server build both exit 0" + - "User sends a message and sees echo-stream tokens appear word-by-word in a streaming assistant bubble (CHAT-01 transport-level, echo-stream placeholder)" + - "Stop button (red square) appears during streaming and cancels the stream on click" + - "Agent selector dropdown in chat header shows agents with colored avatars and persists selection across page reload" + - "Agent badge with colored circle and name appears above each assistant message" + - "Slash command popover appears when typing / prefix, @mention popover appears when typing @ prefix" + - "Agent colors are visually distinguishable across all three themes (Catppuccin Mocha, Tokyo Night, Catppuccin Latte)" artifacts: [] key_links: [] --- @@ -41,12 +46,15 @@ Output: Verified, working Phase 22. Task 1: Full test suite verification and build check - + + (run-only verification task -- no source changes expected; reads test files for diagnostics if failures occur) + server/src/__tests__/chat-stream-routes.test.ts, server/src/__tests__/chat-routes.test.ts, ui/src/lib/parseMessageIntent.test.ts, - ui/src/components/ChatAgentBadge.test.tsx + ui/src/components/ChatAgentBadge.test.tsx, + ui/src/components/ChatInput.slash-mention.test.tsx Run the full test suite and verify all tests pass: @@ -70,42 +78,34 @@ Output: Verified, working Phase 22. pnpm test run && pnpm --filter @paperclipai/ui build && pnpm --filter @paperclipai/server build - - - pnpm test run exits 0 - - pnpm --filter @paperclipai/ui build exits 0 - - pnpm --filter @paperclipai/server build exits 0 - All tests pass and both UI and server build cleanly Task 2: Visual and functional verification of streaming chat - + + (checkpoint -- no files modified; visual/functional verification only) + - Present the verification checklist to the user. The user will manually test the streaming chat experience across all features built in Phase 22: + Present the verification checklist to the user. Complete Phase 22 agent streaming has been built: + SSE echo-stream endpoint, virtualized message list with agent badges, edit/retry actions, + Stop button, AgentSelector, slash command and @mention popovers. + The user will manually test: 1. Start the dev server: `pnpm dev` 2. Open the chat panel (MessageSquare icon in sidebar) - 3. Create a new conversation and send a message — verify tokens stream in word-by-word + 3. Create a new conversation and send a message -- verify tokens stream in word-by-word (echo-stream: you will see your own message echoed back, this is the Phase 22 placeholder; real LLM responses come in Phase 23) 4. While streaming: verify the Stop button (red square) appears; click it to cancel - 5. Hover over an assistant message — verify Retry button (rotate icon) appears; click it - 6. Hover over a user message — verify Edit button (pencil icon) appears; click to enter edit mode, modify text, click Regenerate - 7. Open the Agent Selector dropdown in the header — verify agents appear with colored avatars - 8. Select a different agent — verify it persists (reload page, re-open conversation) - 9. Type `/ask-pm ` — verify slash command popover appears with matching commands - 10. Type `@` followed by an agent name — verify mention popover appears - 11. Switch between all three themes (Catppuccin Mocha, Tokyo Night, Catppuccin Latte) — verify agent badge colors are distinguishable + 5. Hover over an assistant message -- verify Retry button (rotate icon) appears; click it + 6. Hover over a user message -- verify Edit button (pencil icon) appears; click to enter edit mode, modify text, click Regenerate + 7. Open the Agent Selector dropdown in the header -- verify agents appear with colored avatars + 8. Select a different agent -- verify it persists (reload page, re-open conversation) + 9. Type `/ask-pm ` -- verify slash command popover appears with matching commands + 10. Type `@` followed by an agent name -- verify mention popover appears + 11. Switch between all three themes (Catppuccin Mocha, Tokyo Night, Catppuccin Latte) -- verify agent badge colors are distinguishable 12. (Optional) If you have a conversation with many messages, scroll rapidly to check smoothness User types "approved" or describes issues to fix - - - User confirms streaming tokens appear as they are generated - - User confirms Stop button cancels in-progress stream - - User confirms agent badge shows on assistant messages with colored avatar - - User confirms agent selector changes the conversation's agent - - User confirms slash command and @mention popovers appear - - User confirms agent colors are distinguishable across all three themes - User has approved the complete Phase 22 streaming chat experience