--- phase: 33-persistent-memory plan: 03 subsystem: server/routes + ui/pages tags: [ai-streaming, memory-injection, sse, handoff, react, express] requires: - phase: 33-persistent-memory/01 provides: assistantMemoryService (get/append/clear) - phase: 33-persistent-memory/02 provides: PersonalAssistant page, chatApi, useNexusMode - phase: 31-puter-js-zero-config-cloud provides: puterProxyService.chatStream, nexusSettingsService provides: - Real AI streaming via puterProxyService with echo fallback (no puter token) - Memory facts injected as system message prefix in AI calls - Facts appended to memory after each assistant turn - SSE format fixed to {type:"token"}, {type:"done"}, {type:"error"} - POST /conversations/:id/assistant-handoff route - chatApi.assistantHandoff UI method - Wired "Turn into project" button in PersonalAssistant affects: - server/src/routes/chat.ts - server/src/routes/assistant-handoff.ts - server/src/app.ts - ui/src/api/chat.ts - ui/src/pages/PersonalAssistant.tsx tech-stack: added: [] patterns: - Pre-fetch conversation/settings/memory BEFORE flushHeaders (avoids SSE header race) - puterProxyService try/catch for graceful echo fallback - buildHandoffSummary pure function (exported + unit tested separately) - Non-blocking memory append via .catch(() => {}) key-files: created: - server/src/routes/assistant-handoff.ts - server/src/__tests__/33-assistant-handoff.test.ts modified: - server/src/routes/chat.ts - server/src/app.ts - ui/src/api/chat.ts - ui/src/pages/PersonalAssistant.tsx key-decisions: - "Pre-fetch conversation + settings + memory BEFORE res.flushHeaders() to avoid race condition (Pitfall 3 from research)" - "puterProxyService.resolveToken wrapped in try/catch — graceful fallback to streamEcho when no puter token configured" - "buildHandoffSummary exported as named function for direct unit testing without route test harness" - "Personal Assistant handoff navigates to /dashboard after success — PM conversation will appear in conversation list" - "Memory injection skipped for project_builder mode via isAssistant flag from nexus settings" metrics: duration: 20 completed_date: "2026-04-01" tasks_completed: 2 files_changed: 6 --- # Phase 33 Plan 03: AI Streaming + Memory Injection + Handoff Summary **Real AI streaming via puterProxyService with memory-injected system prompt, SSE format fix, and assistant-to-PM handoff route with wired UI button.** ## Performance - **Duration:** ~20 min - **Completed:** 2026-04-01 - **Tasks:** 2 - **Files modified:** 6 ## Accomplishments ### Task 1: Replace streamEcho with real AI streaming + memory injection - `server/src/routes/chat.ts` updated with: - Imports for `assistantMemoryService`, `nexusSettingsService`, `puterProxyService` - Pre-flushHeaders block: resolves conversation, nexus settings, memory facts - `puterProxyService(db).resolveToken()` try/catch for echo fallback - Builds `messagesWithMemory` array: system message with memory facts (capped 2000 chars) + conversation history + new user message - Uses `puterProxyService.chatStream` when token available, falls back to `streamEcho` - **SSE format fixed**: `{type:"token", token}`, `{type:"done", messageId, content}`, `{type:"error", error}` — matches client parser - Non-blocking memory fact append after each assistant turn - Memory injection skipped when mode is `project_builder` ### Task 2: Assistant handoff route and wired UI button (TDD) - RED: `server/src/__tests__/33-assistant-handoff.test.ts` — 7 tests written before implementation (all failing) - GREEN: `server/src/routes/assistant-handoff.ts` created: - `buildHandoffSummary()` pure function: filters user-role messages, concatenates, caps at 1500 chars - `assistantHandoffRoutes(db)`: `POST /conversations/:id/assistant-handoff` with assertBoard auth - Creates new conversation, inserts `handoff_context` system message with user summary - Returns `{ targetConversationId }` - `server/src/app.ts`: `assistantHandoffRoutes(db)` mounted via `api.use()` - `ui/src/api/chat.ts`: `assistantHandoff(conversationId)` method added - `ui/src/pages/PersonalAssistant.tsx`: "Turn into project" button fully wired - `handleHandoff` callback using `chatApi.assistantHandoff` - Success toast + navigate to `/dashboard` - Error toast on failure - Loading spinner during handoff - Button disabled when no conversation selected or handing off ## Task Commits 1. **Task 1: Real AI streaming + memory injection** — `d9a00d25` 2. **Task 2 (RED): Failing tests for handoff route** — `b5028a5d` 3. **Task 2 (GREEN): Handoff route + app wiring + UI button** — `17564d9c` ## Files Created/Modified - `server/src/routes/chat.ts` — Updated stream endpoint with real AI, memory injection, SSE format fix - `server/src/routes/assistant-handoff.ts` — New handoff route with buildHandoffSummary - `server/src/app.ts` — Added assistantHandoffRoutes mount - `server/src/__tests__/33-assistant-handoff.test.ts` — 7 unit tests (4 for buildHandoffSummary, 3 for route handler) - `ui/src/api/chat.ts` — Added assistantHandoff method - `ui/src/pages/PersonalAssistant.tsx` — Wired "Turn into project" button with handoff flow ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking] Prerequisite files missing from worktree** - **Found during:** Task 1 setup - **Issue:** Worktree `worktree-agent-acd379e3` is based on an older commit (4c8cfcd8) that predates phase-21 chat system, phase-30 nexus settings, and phase-33 memory services. Files referenced by this plan did not exist. - **Fix:** Used `git checkout gsd/phase-33-persistent-memory -- ...` to bring 33-01/02 files, and `git checkout gsd/phase-31-puter-js-zero-config-cloud -- ...` for puter-proxy and nexus-settings. Also checked out `pushService.ts` from PAP-878 branch. - **Files:** 18 prerequisite files checked out in Task 1 commit - **Commit:** d9a00d25 **2. [Rule 3 - Blocking] Tooltip component removed from PersonalAssistant imports** - **Found during:** Task 2 (wiring UI button) - **Issue:** The plan called for removing the disabled state from the button. The previous `Tooltip` wrapper was removed since the button is now fully functional (no need for "Coming soon" tooltip). - **Fix:** Removed unused `Tooltip`, `TooltipTrigger`, `TooltipContent` imports; simplified button to direct `onClick` handler. - **Files:** `ui/src/pages/PersonalAssistant.tsx` - **Commit:** 17564d9c ## Known Stubs None — all data paths are fully wired. The `puterProxyService` fallback to `streamEcho` is intentional behavior (not a stub) for users without a configured puter token. ## Test Results - **24 tests pass** across 3 test files: - `33-memory-sanitization.test.ts`: 10 tests - `33-assistant-memory.test.ts`: 7 tests - `33-assistant-handoff.test.ts`: 7 tests (4 buildHandoffSummary + 3 route integration) ## Self-Check: PASSED Files exist: - FOUND: /opt/nexus/.claude/worktrees/agent-acd379e3/server/src/routes/chat.ts - FOUND: /opt/nexus/.claude/worktrees/agent-acd379e3/server/src/routes/assistant-handoff.ts - FOUND: /opt/nexus/.claude/worktrees/agent-acd379e3/server/src/app.ts - FOUND: /opt/nexus/.claude/worktrees/agent-acd379e3/server/src/__tests__/33-assistant-handoff.test.ts - FOUND: /opt/nexus/.claude/worktrees/agent-acd379e3/ui/src/api/chat.ts - FOUND: /opt/nexus/.claude/worktrees/agent-acd379e3/ui/src/pages/PersonalAssistant.tsx Commits: - FOUND: d9a00d25 - FOUND: b5028a5d - FOUND: 17564d9c