diff --git a/CHAT_APPS.md b/CHAT_APPS.md deleted file mode 100644 index f84311a6..00000000 --- a/CHAT_APPS.md +++ /dev/null @@ -1,882 +0,0 @@ -# Chat & ChatHistory Apps Architecture - -**Date**: February 20, 2026 -**Status**: Complete & Running -**Scope**: Unified Pi harness integration for chat functionality - ---- - -## Overview - -The Chat and ChatHistory applications work together to provide a complete conversational interface with session management, grouping, and model selection. They consume the new Pi harness WebSocket API (`/api/pi/chat/ws`) and REST endpoints (`/api/pi/*`). - ---- - -## Directory Structure - -``` -src/ -├── apps/officer-web/ -│ ├── Screens/Dashboard/ -│ │ ├── Chat/ # Chat input/output UI components -│ │ │ ├── index.tsx (empty export) -│ │ │ ├── ChatPanel.tsx (main chat container) -│ │ │ ├── EmbeddableChat.tsx (reusable chat widget) -│ │ │ ├── InputArea.tsx (message input with attachments & voice) -│ │ │ ├── Settings.tsx (model/provider selector) -│ │ │ ├── ChatList/index.tsx (placeholder) -│ │ │ ├── OpenCodeModelPicker.tsx (legacy model picker, keep for now) -│ │ │ └── usePi.ts (WebSocket hook for Pi harness) -│ │ │ -│ │ └── ChatHistory/ # Session & group management UI -│ │ ├── index.tsx (SessionListPage - main router) -│ │ ├── Screen.tsx (SessionList - tree view) -│ │ ├── ChatDetailPanel.tsx (session or new chat detail panel) -│ │ ├── Widget.tsx (sidebar widget showing recent sessions) -│ │ ├── CreateGroupDialog.tsx (new group form) -│ │ ├── GroupContextMenu.tsx (group actions: rename, delete) -│ │ └── SessionContextMenu.tsx (session actions: move, rename, delete) -│ │ -│ └── state/ # State management hooks -│ ├── useChatSessions.ts (REST endpoints: list, get, update, delete, search) -│ ├── useChatGroups.ts (REST endpoints: create, list, update, delete, move) -│ ├── useModels.ts (unified Pi models API) -│ └── useRecentModels.ts (recently used models tracking) -│ -└── workspaces/apps/ # Shared Chat workspace components - ├── Chat/ - │ ├── index.ts (exports: MessageList, MessageBubble, etc.) - │ ├── types.ts (type definitions for all chat types) - │ ├── MessageList.tsx (message history display) - │ ├── MessageBubble.tsx (individual message component) - │ ├── ToolActivity.tsx (tool execution display) - │ ├── QuestionActivity.tsx (question suggestions) - │ └── [other message components] - │ - └── ChatHistory/ - ├── index.ts (exports: SessionListPage, SessionList) - └── SessionBar.tsx (session header bar with nav) -``` - ---- - -## Component Hierarchy - -### Chat Flow - -``` -ChatPanel (main container) - ├── SessionBar (header with back, delete, fullscreen) - └── EmbeddableChat (reusable chat interface) - ├── MessageList (scrollable message history) - │ ├── MessageBubble[] (user/assistant messages) - │ ├── ToolActivity (tool execution results) - │ └── StreamingBubble (real-time assistant text) - │ - └── InputArea (message input) - ├── Attachment controls (image, webpage, file) - ├── Textarea (message input with auto-resize) - ├── Model picker (Settings component) - ├── Send/Stop button - └── Voice recording (Mic button) -``` - -### Session Management Flow - -``` -SessionListPage (routing) - └── WorkspaceLayout (split pane layout) - ├── SessionList (left panel: session tree) - │ ├── Ungrouped sessions - │ └── Grouped sections - │ ├── GroupContextMenu (rename, delete group) - │ └── SessionContextMenu (move, rename, delete session) - │ - └── ChatDetailPanel (right panel: chat view) - ├── SessionChat (existing session) - │ ├── DetailBar (session info, delete button) - │ └── EmbeddableChat (chat interface) - │ - └── NewChat (new conversation) - ├── DetailBar - └── EmbeddableChat -``` - ---- - -## Core Components - -### 1. ChatPanel - -**File**: `Chat/ChatPanel.tsx` - -**Purpose**: Main chat container for the `/chat/:sessionId` route. - -**Key Features**: -- Displays session title in header -- Handles location.state for pre-filled messages and initial sends -- Manages fullscreen toggle -- Passes chat state to EmbeddableChat - -**Props**: -```typescript -type ChatPanelProps = { - chat: ReturnType; - availableModels?: ModelOption[]; -}; -``` - -**Dependencies**: -- `usePi` hook (WebSocket connection) -- `useChatSessions` hook (session data) -- `useSlashCommands` hook (command handling) - ---- - -### 2. EmbeddableChat - -**File**: `Chat/EmbeddableChat.tsx` - -**Purpose**: Reusable, embeddable chat widget used in both ChatPanel and ChatDetailPanel. - -**Key Features**: -- Message display with streaming text -- Input area with attachment handling -- Jump-to-bottom button when user scrolls up -- Auto-scroll to latest message (unless user scrolled) -- Textarea auto-resize -- Webpage scraping and image upload -- Before-send hook for slash commands - -**Key Refs**: -- `scrollViewportRef`: Track scroll position -- `bottomRef`: Auto-scroll target -- `userScrolledRef`: Track if user manually scrolled -- `textareaRef`: Direct textarea access - -**Props**: -```typescript -type EmbeddableChatProps = { - chat: ReturnType; - availableModels?: ModelOption[]; - onBeforeSend?: (text: string) => boolean | Promise; - commandFeedback?: string | null; - defaultInput?: string; - promptPrefix?: string; - className?: string; - cwd?: { root?: string; path: string }; - autoSend?: boolean; -}; -``` - ---- - -### 3. InputArea - -**File**: `Chat/InputArea.tsx` - -**Purpose**: Message input with advanced features (attachments, voice, model selection). - -**Key Features**: -- Textarea with dynamic height -- Attachment dropdown (image, file, URL, PDF) -- Voice recording to WAV format -- Whisper transcription (hardcoded to `http://macmini:8178/inference`) -- Paste-to-attach images -- Model picker with provider selection -- Command feedback display -- Send/Stop button - -**Voice Recording Pipeline**: -1. Start MediaRecorder -2. On stop: Convert to WAV format -3. POST to Whisper: `http://macmini:8178/inference` -4. Append transcribed text to input - -**Dependencies**: -- `sonner` toast notifications -- `lucide-react` icons -- Settings component (model picker) - ---- - -### 4. usePi Hook - -**File**: `Chat/usePi.ts` - -**Purpose**: Central WebSocket connection to Pi harness with message handling. - -**Key Features**: -- Establishes WebSocket connection to `/api/pi/chat/ws` -- Manages message streaming with requestAnimationFrame batching -- Handles all ServerMessage types -- Session initialization and resume -- Debounced message saving (1s) -- Model and CWD tracking - -**Message Type Handling**: - -| Server Message | Handler | Action | -|----------------|---------|--------| -| `session:init` | Sets sessionId, model, cwd; updates URL | -| `assistant:delta` | Accumulates in streaming buffer | -| `assistant:text` | Commits streaming or adds as-is | -| `tool:start` | Creates tool execution message | -| `tool:result` | Updates tool message with output | -| `result` | Commits streaming, adds cost metadata, sets isGenerating=false | -| `sync:messages` | Restores full message history (resume) | -| `error` | Adds error message, sets isGenerating=false | -| `stopped` | Commits streaming, sets isGenerating=false | - -**Streaming Optimization**: -- Uses refs (`streamingRef`) to avoid state thrashing -- RequestAnimationFrame batches updates -- Commits on message boundaries or generation end - -**Options**: -```typescript -type UsePiOptions = { - replaceUrl?: boolean; // auto-update URL on session init - storage?: ResourceChatStorage; // custom storage provider - resourceChatDir?: string; // for resource/task chats - taskInfo?: TaskInfo; // task metadata -}; -``` - -**Return Type**: -```typescript -{ - messages: ChatMessage[]; - streamingText: string; - isConnected: boolean; - isGenerating: boolean; - sessionId: string | null; - model: string | null; - selectedModel: string | null; - cwd: string | null; - setSelectedModel: (modelId: string) => void; - sendPrompt: (text, attachmentIds?, images?, cwd?, groupSlug?) => void; - stopGeneration: () => void; -} -``` - ---- - -### 5. Settings Component - -**File**: `Chat/Settings.tsx` - -**Purpose**: Model selection UI with provider filtering. - -**Key Features**: -- Shows provider buttons before first message -- Switches to dropdown display after first message -- Groups models by provider -- Display names for well-known providers - -**Display Names**: -``` -anthropic → "Anthropic" -openai → "OpenAI" -opencode → "OpenCode Zen" -google → "Google" -groq → "Groq" -... (14 total providers defined) -``` - ---- - -### 6. SessionList (Screen.tsx) - -**File**: `ChatHistory/Screen.tsx` - -**Purpose**: Tree view showing all sessions organized by groups. - -**Key Features**: -- Ungrouped sessions at top -- Collapsible group sections -- Session count badges -- Jump to selected session on render -- Create group and new chat buttons - -**Structure**: -``` -Sessions (Header) -├─ New Chat (button) -└─ New Group (button) - -Ungrouped Sessions -├─ Session 1 -│ ├─ Title + ID (truncated) -│ ├─ Created date/time -│ ├─ Model name -│ └─ Context menu - -Groups (sorted by updatedAt desc) -├─ 📁 Group Name (5 sessions) -│ ├─ Collapse/expand chevron -│ ├─ Group context menu -│ └─ Sessions (nested, indented) -│ ├─ Session A -│ ├─ Session B -│ └─ Context menu per session -``` - -**Styling**: -- Selected session: `border-duck-teal/30 bg-duck-teal/5` -- Unselected: `border-duck-dark/10 bg-background/80` - ---- - -### 7. ChatDetailPanel - -**File**: `ChatHistory/ChatDetailPanel.tsx` - -**Purpose**: Right panel showing either existing session or new chat. - -**Key Features**: -- Session detail bar (title, delete, status indicators) -- Switches between SessionChat and NewChat components -- Handles location.state for initial message/prefill -- Syncs selected session via panel channel - -**Panel States**: -1. No session selected: Empty state message -2. Selected existing session: Load and display conversation -3. Selected 'new': New chat form with optional pre-fill - -**Status Indicators**: -- Red dot: Disconnected -- Orange pulsing dot: Generating -- Green dot: Ready - ---- - -### 8. Group & Session Context Menus - -**GroupContextMenu** (`ChatHistory/GroupContextMenu.tsx`): -- Rename group (inline edit) -- Delete group (confirm dialog) -- Shows sessions will be ungrouped on delete - -**SessionContextMenu** (`ChatHistory/SessionContextMenu.tsx`): -- Move to group (submenu) -- Rename session (inline edit) -- Delete session (direct action) -- Move submenu shows ungrouped + all groups - ---- - -### 9. CreateGroupDialog - -**File**: `ChatHistory/CreateGroupDialog.tsx` - -**Purpose**: Modal for creating new session groups. - -**Features**: -- Name input (required) -- Description input (optional) -- Auto-generated slug (lowercase, kebab-case) -- Form validation -- Error display -- Loading state - -**Slug Generation**: -```javascript -name - .toLowerCase() - .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens - .replace(/^-|-$/g, '') // Remove leading/trailing hyphens -``` - ---- - -## State Management - -### useChatSessions Hook - -**File**: `state/useChatSessions.ts` - -**REST Endpoints**: -- `POST /pi/sessions` - List all sessions for user -- `GET /pi/sessions/{sessionId}` - Get session with messages -- `PUT /pi/sessions/{sessionId}/messages` - Save messages (debounced) -- `PATCH /pi/sessions/{sessionId}` - Rename session -- `DELETE /pi/sessions/{sessionId}` - Delete session -- `GET /pi/sessions/search?q={query}` - Search sessions - -**Query Keys**: -- `['PI_SESSIONS']` - Invalidated on any session change - ---- - -### useChatGroups Hook - -**File**: `state/useChatGroups.ts` - -**REST Endpoints**: -- `GET /pi/groups` - List all groups for user -- `POST /pi/groups` - Create new group -- `PATCH /pi/groups/{slug}` - Update group (name, description) -- `DELETE /pi/groups/{slug}` - Delete group (moves sessions to root) -- `POST /pi/sessions/{sessionId}/move` - Move session to/from group - -**Query Keys**: -- `['PI_GROUPS']` - Invalidated on group changes -- Invalidates `['PI_SESSIONS']` on move/create/delete - ---- - -### useModels Hook - -**File**: `state/useModels.ts` - -**Functions**: -- `usePiModels()` - Get all available models -- `useVisiblePiModels()` - Filter by enabled models in settings - -**REST Endpoint**: -- `GET /pi/models` - List all models - -**Query Key**: `['PI_MODELS']` (5-minute stale time) - ---- - -## Type System - -### Core Types - -**SessionEntry**: -```typescript -{ - id: string; - title: string; - model: string; - cwd: string; - createdAt: number; - updatedAt: number; - messageCount: number; - cost: MessageCost; - groupSlug?: string | null; -} -``` - -**GroupEntry**: -```typescript -{ - name: string; - slug: string; - description?: string; - createdAt: number; - updatedAt: number; - sessionCount: number; -} -``` - -**ChatMessage** (frontend message): -```typescript -| { role: 'user'; text: string; images?: [...] } -| { role: 'assistant'; text: string } -| { role: 'system'; text: string } -| { - role: 'tool'; - toolName: string; - toolInput: Record; - toolCallId: string; - output?: string; - isError?: boolean; - } -| { role: 'result'; cost: MessageCost } -| { role: 'error'; text: string } -``` - -**ServerMessage** (WebSocket protocol): -```typescript -| { type: 'session:init'; sessionId: string; model: string; cwd: string } -| { type: 'assistant:text'; text: string } -| { type: 'assistant:delta'; text: string } -| { type: 'tool:start'; toolCallId: string; toolName: string; toolInput: ... } -| { type: 'tool:result'; toolCallId: string; output: string; isError: boolean } -| { type: 'result'; sessionId: string; cost: MessageCost } -| { type: 'sync:messages'; sessionId: string; messages: Message[]; isGenerating: boolean; streamingText: string } -| { type: 'error'; message: string; errorCode?: string } -| { type: 'stopped' } -``` - ---- - -## Data Flow - -### New Chat - -``` -User clicks "New Chat" - ↓ -SessionListPage sets selected.id = 'new' - ↓ -ChatDetailPanel renders NewChat component - ↓ -NewChat creates usePi() hook (no sessionId) - ↓ -usePi connects to WebSocket, sends first prompt - ↓ -Backend spawns Pi process, sends 'session:init' - ↓ -usePi updates sessionId, URL changes to /chat/{sessionId} - ↓ -SessionListPage re-renders, session appears in list - ↓ -Chat continues normally -``` - -### Resume Existing Session - -``` -User clicks session in SessionList - ↓ -SessionListPage.useEffect sets selected.id = sessionId - ↓ -ChatDetailPanel renders SessionChat with sessionId - ↓ -SessionChat calls usePi(sessionId) hook - ↓ -usePi.useEffect calls getSession(sessionId) - ↓ -Loads messages from server, populates local state - ↓ -User sends first new message - ↓ -usePi sends prompt with sessionId to WebSocket - ↓ -Backend loads session from disk, injects history in system prompt - ↓ -Pi process continues with context -``` - -### Model Switching - -``` -User selects different model in Settings - ↓ -onChange handler calls setSelectedModel(modelId) - ↓ -Next sendPrompt() includes { model: selectedModel } - ↓ -WebSocket sends to backend with model ID - ↓ -New Pi process spawned with specified model - ↓ -session:init message returns new model -``` - -### Attachment Upload - -``` -User clicks image/URL in InputArea - ↓ -EmbeddableChat.handleAttach{Image|Webpage}() - ↓ -POST /scrape or /upload with sessionId/provider - ↓ -Attachment added to state with loading=true - ↓ -Response includes attachmentId - ↓ -Attachment loading=false, displays thumbnail - ↓ -User sends message - ↓ -Attachment prepended to prompt text - ↓ -Server relocates file to session directory -``` - ---- - -## Key Patterns - -### 1. Streaming Text Optimization - -```typescript -const streamingRef = useRef(''); -const rafRef = useRef(null); - -function flushStreaming() { - if (rafRef.current !== null) cancelAnimationFrame(rafRef.current); - rafRef.current = requestAnimationFrame(() => { - setStreamingText(streamingRef.current); - rafRef.current = null; - }); -} - -// On delta: accumulate + flush -streamingRef.current += msg.text; -flushStreaming(); - -// On message end: commit to messages -function commitStreaming() { - if (!streamingRef.current) return; - setMessages((prev) => [...prev, { role: 'assistant', text: streamingRef.current }]); - streamingRef.current = ''; - setStreamingText(''); -} -``` - -**Benefit**: Avoids rendering on every character; batches updates with RAF - -### 2. Auto-Scroll Detection - -```typescript -const userScrolledRef = useRef(false); - -const handleScroll = () => { - const { scrollTop, scrollHeight, clientHeight } = viewport; - const atBottom = scrollHeight - scrollTop - clientHeight < 60; - userScrolledRef.current = !atBottom; - setShowJumpToBottom(!atBottom); -}; - -// Auto-scroll only if user hasn't scrolled up -useEffect(() => { - if (!userScrolledRef.current) { - bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); - } -}, [messages, streamingText]); -``` - -**Benefit**: Respects user's scroll position; auto-scrolls only when at bottom - -### 3. Textarea Auto-Resize - -```typescript -useEffect(() => { - const textarea = textareaRef.current; - if (!textarea) return; - textarea.style.height = 'auto'; - textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px'; -}, [input]); -``` - -**Behavior**: Grows up to 200px, then scrolls internally - -### 4. Debounced Save - -```typescript -useEffect(() => { - if (!sessionIdRef.current || messages.length === 0) return; - - if (saveTimerRef.current !== null) clearTimeout(saveTimerRef.current); - - const sid = sessionIdRef.current; - const snapshot = messages; - saveTimerRef.current = window.setTimeout(() => { - saveMessages(sid, snapshot); - saveTimerRef.current = null; - }, SAVE_DEBOUNCE_MS); - - return () => { - if (saveTimerRef.current !== null) clearTimeout(saveTimerRef.current); - }; -}, [messages]); -``` - -**Benefit**: Saves after 1s of no message changes; avoids spam - ---- - -## Hardcoded Configuration - -### Whisper Transcription - -**Location**: `InputArea.tsx:blobToWav()` and `handleMicClick()` - -**Endpoint**: `http://macmini:8178/inference` - -**Status**: ⚠️ Hardcoded - needs parameterization - -### Session Idle Timeout - -**Location**: Backend (see PI_HARNESS_REBUILD.md) - -**Value**: 1 hour - ---- - -## Known Issues & TODOs - -1. **OpenCodeModelPicker**: Still imported in InputArea but Settings component handles model selection. Can remove if consolidation complete. - -2. **Whisper endpoint**: Hardcoded to `http://macmini:8178`. Should be configurable. - -3. **ChatList placeholder**: `Chat/ChatList/index.tsx` is just a placeholder, contains no actual implementation. - -4. **Legacy type aliases**: In `types.ts` - marked for removal after Phase 9 cleanup. - -5. **Panel channel**: Uses string key `'chat:selected-session'` for panel synchronization. Could be more type-safe. - ---- - -## Integration Points - -### WebSocket (`usePi.ts`) - -**URL**: `/api/pi/chat/ws?token={token}` - -**Protocol**: See `ServerMessage` types above - -**Lifecycle**: -1. Open: empty -2. Message: handle ServerMessage -3. Close: session stays in memory with idle timeout -4. Error: display error message - -### REST API - -**Base**: `/api/pi/` - -**Endpoints**: -- Sessions: `/sessions` (list), `/sessions/{id}` (get/update/delete), `/sessions/search` (search), `/sessions/{id}/move` (move) -- Groups: `/groups` (list/create), `/groups/{slug}` (update/delete) -- Models: `/models` (list) - -**Auth**: Bearer token in Authorization header - -### Storage - -**Cache Keys**: -- `PI_SESSIONS`: SessionEntry[] array -- `PI_GROUPS`: GroupEntry[] array -- `PI_MODELS`: ModelOption[] array - -**Persistence**: TanStack Query with localStorage (default) - ---- - -## Testing Checklist - -- [ ] New chat creates session on backend -- [ ] Resume chat loads messages from server -- [ ] Model switching sends to backend -- [ ] Streaming text appears correctly -- [ ] Tool execution tracked properly -- [ ] Session rename works -- [ ] Session delete removes from list -- [ ] Create group works -- [ ] Move session to group updates counts -- [ ] Delete group moves sessions to root -- [ ] Collapse/expand groups works -- [ ] Fullscreen toggle works -- [ ] Voice recording transcribes to input -- [ ] Image paste/upload works -- [ ] Webpage scrape/attach works -- [ ] Jump-to-bottom button appears when scrolled -- [ ] Auto-scroll respects user scroll -- [ ] Messages debounce save correctly -- [ ] Panel sync between components works -- [ ] URL updates on session init - ---- - -## Future Refactoring Opportunities - -1. **Extract message handlers**: Move ServerMessage handlers to separate utility file -2. **Consolidate model selection**: Remove OpenCodeModelPicker, use only Settings -3. **Streaming abstraction**: Create reusable streaming hook for other features -4. **Panel channel typing**: Create typed version of usePanelChannel -5. **Attachment architecture**: Unified attachment service (currently inline) -6. **Voice transcription**: Move Whisper into dedicated service with configurable endpoint -7. **Message components**: Extract common patterns from MessageBubble, ToolActivity, QuestionActivity -8. **Group UI refinement**: Drag-and-drop reordering, nested group support -9. **Search refinement**: Full-text search, filter by date/model/group -10. **Cost tracking**: Display cumulative cost per session/group - ---- - -## Integration Status - -### ✅ Filled Instances - -All Chat components have been properly integrated throughout the officer-web application: - -1. **Chat/index.tsx** - Now exports: - - `ChatPanel` (main chat container) - - `EmbeddableChat` (reusable chat widget) - - `Attachment` type - - `InputArea` (message input) - - `Settings` (model selector) - - `usePi` (WebSocket hook) - - `ChatList` (session list) - -2. **ChatList Component** - Fully implemented: - - Displays all sessions - - Shows creation date/time - - Shows model name - - Links to individual sessions - -3. **ChatHistory/index.tsx** - Exports: - - `SessionListPage` (main page component) - - `SessionList` (session tree view) - - `ChatHistory` (sidebar widget) - -4. **App Registry Integration** - Fully configured: - - ✅ `chat` - ChatPanel widget - - ✅ `chat-history` - SessionHistory navigation - - ✅ `chat-launcher` - Quick chat launcher - - All registered in Workspaces/app-registry.tsx - -5. **Dashboard Routes** - Complete: - - ✅ `/chat` - SessionListPage root - - ✅ `/chat/new` - New chat - - ✅ `/chat/:sessionId` - Resume session - - All wired to SessionListPage - -6. **Integration Points**: - - ✅ ChatPanel (ChatHistory/ChatDetailPanel.tsx) - - ✅ EmbeddableChat (Files/TaskRunnerModal.tsx) - - ✅ usePi hook (CapabilityPage.tsx) - - ✅ Model selection (Settings.tsx) - - ✅ Task runner modal (Files/Screen/TaskRunnerModal.tsx) - - ✅ Automation edit chat (Automation/AutomationEditChat.tsx) - -### Type Exports - -All Chat-related types properly exported from `workspaces/apps/Chat/types.ts`: -- ✅ `ChatMessage` (union type for all message variants) -- ✅ `ServerMessage` (WebSocket protocol types) -- ✅ `Message` (backend message format) -- ✅ `SessionEntry` (session metadata) -- ✅ `GroupEntry` (group metadata) -- ✅ `MessageCost` (cost tracking) -- ✅ `ModelOption` (model definition) -- ✅ `TaskInfo` (task metadata) - -### State Management - -All state hooks properly integrated: -- ✅ `useChatSessions` - REST endpoints for sessions -- ✅ `useChatGroups` - REST endpoints for groups -- ✅ `usePiModels` - Model listing -- ✅ `useVisiblePiModels` - Filtered models by settings - -### Compilation Status - -✅ No Chat-related TypeScript errors -✅ All imports resolve correctly -✅ Type definitions align with WebSocket protocol - ---- - -## Summary - -The Chat & ChatHistory apps form a cohesive system for conversational AI interaction: - -- **Chat** components handle real-time messaging and user input -- **ChatHistory** components manage session persistence and organization -- **usePi** hook bridges frontend and Pi harness backend -- **State hooks** provide clean REST API abstraction -- **Type system** ensures message protocol consistency -- **Streaming optimization** prevents UI thrashing -- **Grouping feature** organizes conversations logically -- **All instances filled** and properly integrated across the dashboard - -The architecture is clean, modular, and fully integrated throughout the application. Ready for the major refactor. diff --git a/CHAT_FILL_COMPLETE.txt b/CHAT_FILL_COMPLETE.txt deleted file mode 100644 index ccb67e12..00000000 --- a/CHAT_FILL_COMPLETE.txt +++ /dev/null @@ -1,170 +0,0 @@ -================================================================================ -CHAT INTEGRATION COMPLETION REPORT -================================================================================ - -Date: February 20, 2026 -Status: ✅ COMPLETE -Task: Fill all instances where Chat is required in src/apps/officer-web - -================================================================================ -SUMMARY -================================================================================ - -All Chat components and integrations in officer-web have been identified, -verified, and properly filled. The application is now ready for the major -refactoring effort. - -✅ 2 files modified -✅ 40+ files verified -✅ 0 compilation errors -✅ 0 import resolution errors -✅ 100% integration coverage - -================================================================================ -CHANGES MADE -================================================================================ - -1. Chat/index.tsx - BEFORE: export {}; - AFTER: 6 component exports + 1 hook export - - ChatPanel (main container) - - EmbeddableChat (reusable widget) - - InputArea (message input) - - Settings (model selector) - - usePi (WebSocket hook) - - ChatList (session list) - -2. Chat/ChatList/index.tsx - BEFORE: Placeholder showing just "ChatList" - AFTER: Full implementation with: - - Session listing from useChatSessions - - Session metadata display - - Links to individual sessions - - Empty state handling - - Responsive styling - -================================================================================ -VERIFICATION RESULTS -================================================================================ - -✅ Module Exports - - Chat/index.tsx: 6 items - - ChatHistory/index.tsx: SessionListPage, SessionList, ChatHistory - - Dashboard/index.tsx: All exports - -✅ Type Definitions - - ChatMessage (frontend) - - ServerMessage (WebSocket) - - Message (backend) - - SessionEntry (metadata) - - GroupEntry (metadata) - - MessageCost (cost tracking) - - ModelOption (AI model) - - TaskInfo (task context) - + Legacy types for migration - -✅ State Management - - useChatSessions (session REST API) - - useChatGroups (group REST API) - - usePiModels (model listing) - - useVisiblePiModels (filtered models) - - useRecentModels (recently used) - -✅ Routes - - /chat → SessionListPage - - /chat/new → SessionListPage (new mode) - - /chat/:sessionId → SessionListPage (resume) - -✅ Workspace Registry - - 'chat' app → ChatWidget - - 'chat-history' app → ChatHistory - - 'chat-launcher' app → ChatLauncher - -✅ Integration Points - - ChatDetailPanel uses ChatPanel + EmbeddableChat - - TaskRunnerModal uses EmbeddableChat - - CapabilityPage uses EmbeddableChat - - AutomationEditChat uses CapabilityChat - - HomeScreen uses chat-launcher panel - - SessionList uses ChatList + groups - -✅ Compilation - - No Chat-related TypeScript errors - - All imports resolve correctly - - Type consistency verified - -================================================================================ -DOCUMENTATION CREATED -================================================================================ - -1. CHAT_APPS.md (22,110 bytes) - - Complete architectural overview - - Component hierarchy - - Data flow diagrams - - Integration points - - Type system documentation - - Key patterns and design decisions - -2. CHAT_INTEGRATION_SUMMARY.md (8,846 bytes) - - What was changed - - Complete integration map - - Dependency tree - - Verification checklist - - Next steps for refactor - -3. CHAT_INTEGRATION_CHECKLIST.md (9,792 bytes) - - Module exports verification - - State management verification - - Type definitions verification - - Integration points verification - - Compilation status - - Features implemented - - Testing status - -4. CHAT_FILL_COMPLETE.txt (this file) - - Executive summary - -================================================================================ -READY FOR REFACTORING -================================================================================ - -All Chat instances have been identified and filled. The architecture is solid -and components are properly integrated. You can now proceed with confidence -on the major refactoring effort. - -Recommended next steps: -1. Review CHAT_APPS.md for architectural understanding -2. Use CHAT_INTEGRATION_CHECKLIST.md as verification reference -3. Proceed with major refactoring as planned -4. Refer to dependency trees when making architectural changes - -================================================================================ -FILES MODIFIED -================================================================================ - -Modified: - src/apps/officer-web/Screens/Dashboard/Chat/index.tsx - src/apps/officer-web/Screens/Dashboard/Chat/ChatList/index.tsx - -Verified/Complete: - 40+ supporting files across Chat, ChatHistory, Home, Files, - Settings, Automation, Workspaces, Projects, and state management - -Documentation: - CHAT_APPS.md - CHAT_INTEGRATION_SUMMARY.md - CHAT_INTEGRATION_CHECKLIST.md - CHAT_FILL_COMPLETE.txt - -================================================================================ -NEXT STEPS -================================================================================ - -1. Review the documentation created -2. Confirm all changes meet your refactoring requirements -3. Proceed with major refactoring -4. Use documentation as reference during refactoring - -Contact: Reference the CHAT_* documentation files for detailed information. - -================================================================================ diff --git a/CHAT_INTEGRATION_CHECKLIST.md b/CHAT_INTEGRATION_CHECKLIST.md deleted file mode 100644 index 70daf881..00000000 --- a/CHAT_INTEGRATION_CHECKLIST.md +++ /dev/null @@ -1,357 +0,0 @@ -# Chat Integration Completion Checklist - -**Date**: February 20, 2026 -**Status**: ✅ 100% COMPLETE - ---- - -## Module Exports Verification - -### Chat Components -- [x] ChatPanel - Main chat container - - File: `src/apps/officer-web/Screens/Dashboard/Chat/ChatPanel.tsx` - - Status: ✅ Exported from Chat/index.tsx - -- [x] EmbeddableChat - Reusable chat widget - - File: `src/apps/officer-web/Screens/Dashboard/Chat/EmbeddableChat.tsx` - - Status: ✅ Exported from Chat/index.tsx - - Type: `Attachment` also exported - -- [x] InputArea - Message input component - - File: `src/apps/officer-web/Screens/Dashboard/Chat/InputArea.tsx` - - Status: ✅ Exported from Chat/index.tsx - -- [x] Settings - Model selector component - - File: `src/apps/officer-web/Screens/Dashboard/Chat/Settings.tsx` - - Status: ✅ Exported from Chat/index.tsx - -- [x] usePi - WebSocket hook - - File: `src/apps/officer-web/Screens/Dashboard/Chat/usePi.ts` - - Status: ✅ Exported from Chat/index.tsx - -- [x] ChatList - Session list component - - File: `src/apps/officer-web/Screens/Dashboard/Chat/ChatList/index.tsx` - - Status: ✅ Implemented & exported from Chat/index.tsx - -### ChatHistory Components -- [x] SessionListPage - Main routing component - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx` - - Status: ✅ Exported from ChatHistory/index.tsx - -- [x] SessionList - Session tree view - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx` - - Status: ✅ Exported from ChatHistory/index.tsx - -- [x] ChatDetailPanel - Session detail display - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/ChatDetailPanel.tsx` - - Status: ✅ Used internally in SessionListPage - -- [x] ChatHistory (Widget) - Sidebar widget - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx` - - Status: ✅ Exported as ChatHistoryApp from ChatHistory/index.tsx - -- [x] CreateGroupDialog - Group creation modal - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx` - - Status: ✅ Used in SessionList - -- [x] GroupContextMenu - Group actions - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/GroupContextMenu.tsx` - - Status: ✅ Used in SessionList - -- [x] SessionContextMenu - Session actions - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/SessionContextMenu.tsx` - - Status: ✅ Used in SessionList - ---- - -## State Management Verification - -### Hooks -- [x] useChatSessions - - File: `src/apps/officer-web/state/useChatSessions.ts` - - Endpoints: POST/GET/PATCH/DELETE /pi/sessions - - Status: ✅ Complete - -- [x] useChatGroups - - File: `src/apps/officer-web/state/useChatGroups.ts` - - Endpoints: GET/POST/PATCH/DELETE /pi/groups - - Status: ✅ Complete - -- [x] useModels (Pi) - - File: `src/apps/officer-web/state/useModels.ts` - - Functions: usePiModels, useVisiblePiModels - - Status: ✅ Complete - -- [x] useRecentModels - - File: `src/apps/officer-web/state/useRecentModels.ts` - - Status: ✅ Used in model picker - ---- - -## Type Definitions Verification - -### Core Types -- [x] ChatMessage - Frontend message union - - File: `src/workspaces/apps/Chat/types.ts` - - Status: ✅ Exported - -- [x] ServerMessage - WebSocket protocol - - File: `src/workspaces/apps/Chat/types.ts` - - Status: ✅ Exported - -- [x] Message - Backend storage format - - File: `src/workspaces/apps/Chat/types.ts` - - Status: ✅ Exported - -- [x] SessionEntry - Session metadata - - File: `src/workspaces/apps/Chat/types.ts` - - Status: ✅ Exported - -- [x] GroupEntry - Group metadata - - File: `src/workspaces/apps/Chat/types.ts` - - Status: ✅ Exported - -- [x] MessageCost - Cost tracking - - File: `src/workspaces/apps/Chat/types.ts` - - Status: ✅ Exported - -- [x] ModelOption - AI model definition - - File: `src/workspaces/apps/Chat/types.ts` - - Status: ✅ Exported - -- [x] TaskInfo - Task context - - File: `src/workspaces/apps/Chat/types.ts` - - Status: ✅ Exported - -### Legacy Types (for migration) -- [x] LegacyChatMessage - - Status: ✅ Defined for backward compatibility - -- [x] LegacySessionEntry - - Status: ✅ Defined for backward compatibility - -- [x] LegacyServerMessage - - Status: ✅ Defined for backward compatibility - ---- - -## Integration Points Verification - -### Routes -- [x] `/chat` → SessionListPage - - File: `src/apps/officer-web/App.tsx` - - Status: ✅ Routed - -- [x] `/chat/new` → SessionListPage with isNew=true - - File: `src/apps/officer-web/App.tsx` - - Status: ✅ Routed - -- [x] `/chat/:sessionId` → SessionListPage with sessionId - - File: `src/apps/officer-web/App.tsx` - - Status: ✅ Routed - -### Workspace Registry -- [x] 'chat' app → ChatWidget - - File: `src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx` - - Status: ✅ Registered - -- [x] 'chat-history' app → ChatHistory - - File: `src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx` - - Status: ✅ Registered - -- [x] 'chat-launcher' app → ChatLauncher - - File: `src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx` - - Status: ✅ Registered - -### Component Usages -- [x] ChatDetailPanel uses ChatPanel + EmbeddableChat - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/ChatDetailPanel.tsx` - - Status: ✅ Integrated - -- [x] TaskRunnerModal uses EmbeddableChat - - File: `src/apps/officer-web/Screens/Dashboard/Files/Screen/TaskRunnerModal.tsx` - - Status: ✅ Integrated - -- [x] CapabilityPage uses EmbeddableChat - - File: `src/apps/officer-web/Screens/Dashboard/CapabilityPage.tsx` - - Status: ✅ Integrated - -- [x] AutomationEditChat uses CapabilityChat - - File: `src/apps/officer-web/Screens/Dashboard/Automation/AutomationEditChat.tsx` - - Status: ✅ Integrated - -- [x] HomeScreen uses chat-launcher panel - - File: `src/apps/officer-web/Screens/Dashboard/Home/index.tsx` - - Status: ✅ Integrated - -- [x] SessionList uses ChatList + groups - - File: `src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx` - - Status: ✅ Integrated - ---- - -## Compilation & Type Safety - -### TypeScript -- [x] No Chat-related compilation errors - - Command: `bun run tsc --noEmit` - - Status: ✅ PASS - -- [x] All Chat imports resolve correctly - - Status: ✅ PASS - -- [x] All Chat types are consistent - - Status: ✅ PASS - -- [x] No missing type definitions - - Status: ✅ PASS - -### Module Resolution -- [x] `@/Screens/Dashboard/Chat` resolves correctly - - Status: ✅ PASS - -- [x] `@/Screens/Dashboard/ChatHistory` resolves correctly - - Status: ✅ PASS - -- [x] `apps/Chat` resolves correctly - - Status: ✅ PASS - -- [x] `apps/ChatHistory` resolves correctly - - Status: ✅ PASS - ---- - -## Dashboard Exports - -- [x] Dashboard/index.tsx exports Chat - - Status: ✅ Exported via `export * from './Chat'` - -- [x] Dashboard/index.tsx exports ChatHistory - - Status: ✅ Exported via `export * from './ChatHistory'` - -- [x] Dashboard/Layout exported - - Status: ✅ Exported via `export * from './Layout'` - -- [x] All screen exports present - - Status: ✅ Complete list exported - ---- - -## Features Implemented - -### ChatPanel -- [x] Displays session title -- [x] Shows connection status -- [x] Handles generation status -- [x] Fullscreen toggle -- [x] Delete session action -- [x] Handles location.state for initial messages -- [x] Integrates with slash commands - -### EmbeddableChat -- [x] Displays message list -- [x] Shows streaming text -- [x] Auto-scroll to bottom -- [x] Jump-to-bottom button -- [x] User scroll detection -- [x] Textarea auto-resize -- [x] Attachment handling -- [x] Before-send hook - -### InputArea -- [x] Message textarea -- [x] Send/Stop buttons -- [x] Attachment dropdown -- [x] Image upload -- [x] URL scraping -- [x] Voice recording -- [x] Whisper transcription -- [x] Model selector -- [x] Provider switching -- [x] Command feedback display - -### SessionList -- [x] Display all sessions -- [x] Group by folder -- [x] Collapse/expand groups -- [x] Session count badges -- [x] Create group button -- [x] New chat button -- [x] Context menus -- [x] Selection highlighting - -### ChatList -- [x] Display all sessions (new) -- [x] Show creation date -- [x] Show model name -- [x] Empty state -- [x] Links to sessions -- [x] Icon display -- [x] Responsive styling - ---- - -## Testing Status - -All components verified to work with: -- [x] Bun package manager -- [x] TypeScript compiler -- [x] Module resolution system -- [x] React Router integration -- [x] React Query integration -- [x] TanStack React Query -- [x] WebSocket connections -- [x] REST API calls -- [x] Component composition -- [x] Type checking - ---- - -## Ready for Refactoring - -✅ **All Chat instances have been identified and filled** -✅ **All components are properly exported** -✅ **All integrations are verified** -✅ **Type system is complete** -✅ **No compilation errors** -✅ **All imports resolve correctly** -✅ **All exports are in place** -✅ **All features are implemented** - -**Status**: READY FOR MAJOR REFACTOR - ---- - -## Files Summary - -### Modified (2) -1. `src/apps/officer-web/Screens/Dashboard/Chat/index.tsx` - Added exports -2. `src/apps/officer-web/Screens/Dashboard/Chat/ChatList/index.tsx` - Implemented component - -### Verified Complete (40+) -- Chat components (6) -- ChatHistory components (7) -- State management hooks (4) -- Type definitions (15) -- Integration files (10+) - -### Total Coverage -- ✅ 100% of Chat module required instances -- ✅ 100% of ChatHistory module required instances -- ✅ 100% of integration points -- ✅ 100% of type definitions -- ✅ 100% of routing -- ✅ 100% of state management - ---- - -## Next: Major Refactor - -Now that all Chat instances are filled and verified, you can proceed with confidence on the major refactoring discussed. The architecture is solid and all components are properly integrated. - -Recommended refactoring areas: -1. Streaming optimization abstraction -2. Message handler separation -3. Type safety improvements -4. Component consolidation -5. Feature enhancements diff --git a/CHAT_INTEGRATION_SUMMARY.md b/CHAT_INTEGRATION_SUMMARY.md deleted file mode 100644 index e35934de..00000000 --- a/CHAT_INTEGRATION_SUMMARY.md +++ /dev/null @@ -1,321 +0,0 @@ -# Chat Integration Summary - -**Date**: February 20, 2026 -**Status**: ✅ COMPLETE -**Scope**: Fill all instances where Chat components are required in officer-web - ---- - -## What Was Done - -### 1. Chat Module Exports (Chat/index.tsx) - -**File**: `src/apps/officer-web/Screens/Dashboard/Chat/index.tsx` - -**Changed From**: -```typescript -export {}; -``` - -**Changed To**: -```typescript -export { ChatPanel } from './ChatPanel'; -export { EmbeddableChat, type Attachment } from './EmbeddableChat'; -export { InputArea } from './InputArea'; -export { Settings } from './Settings'; -export { usePi } from './usePi'; -export { ChatList } from './ChatList'; -``` - -**Impact**: Now all Chat components are properly exported and available for import via `@/Screens/Dashboard/Chat` - ---- - -### 2. ChatList Implementation (Chat/ChatList/index.tsx) - -**File**: `src/apps/officer-web/Screens/Dashboard/Chat/ChatList/index.tsx` - -**Changed From**: Placeholder that just displayed "ChatList" - -**Changed To**: Fully functional component that: -- ✅ Displays all chat sessions from useChatSessions hook -- ✅ Shows session title, creation date, and model name -- ✅ Renders session count when empty -- ✅ Links to individual sessions via `/chat/{sessionId}` -- ✅ Uses duck-teal color scheme for consistency -- ✅ Shows message icon and metadata -- ✅ Responsive styling with hover states - -**Features**: -```typescript -export const ChatList = () => { - const { sessions } = useChatSessions(); - - if (sessions.length === 0) { - return ; - } - - return ( -
- {sessions.map((session) => ( - - // Session item with title, date, model - - ))} -
- ); -}; -``` - ---- - -## Complete Integration Map - -### Routes & Components - -``` -/chat → SessionListPage - ├── SessionList (left panel) - │ ├── ChatList (displays sessions) - │ ├── Create Group button - │ └── New Chat button - │ - └── ChatDetailPanel (right panel) - ├── SessionChat (existing sessions) - │ ├── DetailBar (session info) - │ └── EmbeddableChat - │ - └── NewChat (new conversation) - ├── DetailBar - └── EmbeddableChat - -/chat/new → SessionListPage with isNew=true -/chat/:sessionId → SessionListPage with selected session -``` - -### Workspace Registry - -**File**: `src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx` - -**Registered Apps**: -```typescript -appRegistry = { - 'chat': ChatWidget, // Main chat panel - 'chat-history': ChatHistory, // Session management - 'chat-launcher': ChatLauncher, // Quick launcher from home - ... -} -``` - -### Integration Points Verified - -1. **ChatDetailPanel** (ChatHistory/ChatDetailPanel.tsx) - - ✅ Uses `usePi` from Chat/usePi - - ✅ Uses `EmbeddableChat` from Chat/EmbeddableChat - - ✅ Uses `useVisiblePiModels` from state - - ✅ Properly handles session and new chat flows - -2. **TaskRunnerModal** (Files/Screen/TaskRunnerModal.tsx) - - ✅ Uses `usePi` with taskInfo option - - ✅ Uses `EmbeddableChat` with task context - - ✅ Imports `TaskInfo` type from apps/Chat - -3. **CapabilityPage** (CapabilityPage.tsx) - - ✅ Uses `usePi` with replaceUrl=false - - ✅ Uses `EmbeddableChat` for capability editing - - ✅ Integrates with resource directory context - -4. **AutomationEditChat** (Automation/AutomationEditChat.tsx) - - ✅ Uses `CapabilityChat` from CapabilityPage - - ✅ Handles delete and close operations - - ✅ Integrates with automation workflow - -5. **App Registry** (Workspaces/app-registry.tsx) - - ✅ ChatWidget properly instantiated with usePi hook - - ✅ Chat models passed via useVisiblePiModels - - ✅ Registered as embeddable workspace app - -### Type Exports - -All types properly exported from `workspaces/apps/Chat/types.ts`: - -```typescript -// Message Types -ChatMessage // Frontend message representation -ServerMessage // WebSocket protocol -Message // Backend storage format - -// Entity Types -SessionEntry // Chat session metadata -GroupEntry // Session group metadata -MessageCost // Token/cost tracking -ModelOption // AI model definition -TaskInfo // Task execution context - -// Legacy Types (for migration) -LegacyChatMessage -LegacySessionEntry -LegacyServerMessage -``` - -**Exported Via**: -- `apps/Chat` (workspaces/apps/Chat/index.ts) -- `@/state/useModels` (ModelOption) -- `@/state/useChatSessions` (SessionEntry, Message) -- `@/state/useChatGroups` (GroupEntry) - ---- - -## Dependency Map - -### Chat Components Import Tree - -``` -Chat/index.tsx (exports) -├── ChatPanel -│ ├── SessionBar (from apps/ChatHistory) -│ ├── EmbeddableChat -│ ├── usePi -│ ├── useChatSessions -│ └── useSlashCommands -│ -├── EmbeddableChat -│ ├── MessageList (from apps/Chat) -│ ├── InputArea -│ ├── useChatWebSocket (from hooks) -│ ├── useClient (from hooks) -│ └── toast (from sonner) -│ -├── InputArea -│ ├── Settings -│ ├── MediaRecorder API -│ ├── Whisper transcription -│ └── Attachment handling -│ -├── Settings -│ ├── Model selector -│ ├── Provider display names -│ └── Model filtering -│ -├── usePi -│ ├── useChatWebSocket (from hooks) -│ ├── useChatSessions (for resume) -│ ├── useRef + useState -│ └── requestAnimationFrame -│ -└── ChatList - └── useChatSessions -``` - ---- - -## Files Modified - -### 1. Chat/index.tsx -- **Before**: Empty export -- **After**: Full export of all Chat components and hooks -- **Lines**: 1 → 6 - -### 2. Chat/ChatList/index.tsx -- **Before**: Placeholder component -- **After**: Fully functional session list -- **Lines**: 1 → 50 (complete implementation) - ---- - -## Files Already Complete - -### Exports -- ✅ ChatHistory/index.tsx (SessionListPage, SessionList, ChatHistory) -- ✅ CapabilityPage.tsx (CapabilityChat, CapabilityPage) -- ✅ Workspaces/app-registry.tsx (ChatWidget registration) -- ✅ Home/index.tsx (HomeScreen with chat-launcher panel) -- ✅ Files/index.tsx (FilesPage) -- ✅ Dashboard/index.tsx (all screen exports) - -### Components -- ✅ ChatPanel.tsx (main container) -- ✅ EmbeddableChat.tsx (reusable widget) -- ✅ InputArea.tsx (message input) -- ✅ Settings.tsx (model selector) -- ✅ usePi.ts (WebSocket hook) -- ✅ ChatDetailPanel.tsx (session detail view) -- ✅ SessionList.tsx (session tree) -- ✅ CreateGroupDialog.tsx (group creation) -- ✅ GroupContextMenu.tsx (group management) -- ✅ SessionContextMenu.tsx (session management) -- ✅ TaskRunnerModal.tsx (task chat modal) - -### State Hooks -- ✅ useChatSessions.ts (session REST API) -- ✅ useChatGroups.ts (group REST API) -- ✅ useModels.ts (model listing) - -### Type Definitions -- ✅ workspaces/apps/Chat/types.ts (all message types) -- ✅ workspaces/apps/Chat/index.ts (type exports) - ---- - -## Verification Checklist - -### Compilation -- ✅ No Chat-related TypeScript errors -- ✅ All imports resolve correctly -- ✅ Type definitions are consistent - -### Exports -- ✅ Chat/index.tsx exports 6 items -- ✅ ChatHistory/index.tsx exports SessionListPage -- ✅ Dashboard/index.tsx exports Chat and ChatHistory -- ✅ App.tsx routes to SessionListPage - -### Integration -- ✅ ChatPanel used in ChatDetailPanel -- ✅ EmbeddableChat used in TaskRunnerModal -- ✅ usePi used in CapabilityPage -- ✅ ChatWidget registered in app-registry -- ✅ Models integrated in Settings -- ✅ Groups integrated in SessionList - -### Types -- ✅ SessionEntry exported and used -- ✅ GroupEntry exported and used -- ✅ ChatMessage exported and used -- ✅ ServerMessage exported and used -- ✅ TaskInfo exported and used -- ✅ ModelOption exported and used - ---- - -## Next Steps (For Refactor) - -Now that all Chat instances are filled and integrated, you can proceed with the major refactor: - -1. **Message Handlers Extraction**: Move ServerMessage handlers from usePi to separate utilities -2. **Streaming Abstraction**: Create reusable hook for streaming text optimization -3. **Component Consolidation**: Consider merging redundant UI patterns -4. **Model Selection Unification**: Remove legacy OpenCodeModelPicker if still present -5. **Type Safety Improvements**: Convert panel channel to typed version -6. **Feature Enhancement**: Add drag-and-drop for session reordering -7. **Performance Optimization**: Consider virtual scrolling for large session lists -8. **Search Enhancement**: Implement full-text search across messages - ---- - -## Summary - -✅ **All Chat instances have been identified and filled** -✅ **All components properly exported** -✅ **All integrations verified** -✅ **Type system complete** -✅ **No compilation errors** -✅ **Ready for refactoring** - -The Chat application is now fully integrated throughout officer-web with: -- 6 core Chat components properly exported -- 1 new ChatList implementation -- Complete type system -- Full integration with routing, state management, and other features -- Clean separation of concerns -- Ready for major architectural refactor diff --git a/Email.md b/Email.md deleted file mode 100644 index e69de29b..00000000 diff --git a/HANDOFF.md b/HANDOFF.md deleted file mode 100644 index db027a20..00000000 --- a/HANDOFF.md +++ /dev/null @@ -1,189 +0,0 @@ -# Claude Web Interface — Handoff Document - -## What Was Built - -A web-based chat interface at `/claude` that lets the user talk to Claude Code through the browser. Claude Code runs on the same machine as the server via the **Claude Agent SDK** (`@anthropic-ai/claude-agent-sdk`), with full access to the monorepo filesystem. Results stream back to the browser in real time over WebSocket. - -Additionally, a `/plans` page renders markdown plan documents from a `plans/` directory. - ---- - -## Architecture - -``` -Browser (officer-web) Bun Server Same Machine -┌──────────────┐ WebSocket ┌───────────────────┐ Claude Agent SDK ┌─────────────┐ -│ /claude page │◄────────────►│ /api/claude/ws │◄──────────────────►│ Claude Code │ -│ │ │ │ │ │ -│ - Chat input │ JSON msgs │ - JWT auth on │ async generator │ - File I/O │ -│ - Messages │◄────────────►│ upgrade │◄──────────────────►│ - Bash │ -│ - Tool calls │ │ - Bridge: SDK ↔ WS │ │ - Search │ -│ - Streaming │ │ - Session tracking │ │ - Web fetch │ -└──────────────┘ └───────────────────┘ └─────────────┘ -``` - -### Data Flow - -1. User sends prompt via browser → WebSocket JSON message `{ type: 'chat', prompt, sessionId? }` -2. Server calls `query()` from Claude Agent SDK with the prompt (and `resume: sessionId` if continuing) -3. SDK returns an async generator of `SDKMessage` objects -4. Server iterates the generator, translating each SDK message into our protocol and sending over WS -5. Frontend accumulates messages into React state and renders them - -### Session Persistence - -- **SDK side**: The Agent SDK handles full conversation context internally via `resume: sessionId` -- **Frontend side**: Messages are persisted in `localStorage` keyed by session ID (`claude_session_{id}`) -- **Session index**: A separate `claude_sessions` key in localStorage stores `{ id, title, createdAt }[]` -- **URL**: Session ID is pushed to the URL via `window.history.replaceState` (not React Router navigate, to avoid remounting) - ---- - -## File Map - -### Backend — WebSocket Bridge - -| File | Purpose | -|------|---------| -| `src/servers/api/claude/types.ts` | `ClientMessage` and `ServerMessage` union types for the WS protocol | -| `src/servers/api/claude/websocket.ts` | Bun `WebSocketHandler` — bridges browser WS ↔ Claude Agent SDK `query()` | - -**Key details of `websocket.ts`:** -- Per-connection state tracked in a `Map` (abortController, currentSessionId) -- `handleChat()` calls `query()` with `permissionMode: 'bypassPermissions'`, `systemPrompt: { type: 'preset', preset: 'claude_code' }`, `settingSources: ['project']`, `includePartialMessages: true` -- Iterates the async generator, maps SDK message types to our protocol: - - `system` (subtype `init`) → `session:init` - - `assistant` → loops content blocks: `text` → `assistant:text`, `tool_use` → `tool:use` - - `user` → loops content blocks: `tool_result` → `tool:result` - - `stream_event` (content_block_delta/text_delta) → `assistant:partial` - - `result` → sends `result.result` as `assistant:text` fallback, then `result` -- `stop` message aborts via `AbortController` -- Has `console.log` debug statements (prefixed `[claude-ws]`) — can be removed once stable - -### Backend — Server Wiring - -| File | Purpose | -|------|---------| -| `src/server.tsx` | Added `/api/claude/ws` route for WS upgrade + `websocket: claudeWebsocket` handler | -| `src/servers/hono.ts` | Added `plansRouter` to protected routes | - -**WS Auth** (in `server.tsx`): -- Browsers can't set headers on WS upgrade, so JWT is passed via `?token=` query param -- Verifies token with `verify()` from `src/servers/jwt.ts` -- Checks token blacklist (same logic as `user-middleware.ts`) -- On success, upgrades with `{ data: { userId } }` - -### Backend — Plans API - -| File | Purpose | -|------|---------| -| `src/servers/api/plans/plans.ts` | `GET /api/plans` lists plan names, `GET /api/plans/:name` returns markdown text | -| `plans/claude-web-interface.md` | The plan document for this feature | - -### Frontend — Claude Chat - -All in `src/apps/officer-web/Screens/Dashboard/Claude/`: - -| File | Purpose | -|------|---------| -| `types.ts` | `SessionEntry`, `ChatMessage` (union: user/assistant/tool/result/error), `ServerMessage` | -| `useClaude.ts` | Core hook: WS connection, message state, streaming, localStorage persistence, session index | -| `index.tsx` | Screen entry: shows `SessionList` on `/claude`, `ChatPanel` on `/claude/:sessionId` | -| `ChatPanel.tsx` | Full chat UI: session bar, scrollable messages, auto-resize textarea, send/stop buttons | -| `MessageBubble.tsx` | Renders messages by role. Assistant text uses `react-markdown` + `remark-gfm` + `rehype-raw`. Includes `StreamingBubble` with blinking cursor | -| `ToolActivity.tsx` | Collapsible tool call display with per-tool icons, input/output preview, expand/collapse | -| `SessionList.tsx` | Lists previous sessions from localStorage index. Click to open, delete button on hover, "New Chat" button | - -**Key details of `useClaude.ts`:** -- Accepts optional `initialSessionId` from URL params -- Loads messages from localStorage on mount if resuming -- Connects WS to `/api/claude/ws?token={bearer}` with exponential backoff reconnect -- Streaming text accumulated in a ref, flushed to state via `requestAnimationFrame` to avoid render thrashing -- `sendPrompt()` uses a `sessionIdRef` (always current, no stale closure) to send the sessionId -- `session:init` → stores sessionId, registers in session index, updates URL via `history.replaceState` -- `newSession()` → clears state, resets URL to `/claude` -- Messages auto-saved to localStorage on every change - -### Frontend — Plans Page - -| File | Purpose | -|------|---------| -| `src/apps/officer-web/Screens/Dashboard/Plans/index.tsx` | Fetches plan list + selected plan markdown, renders with react-markdown | - -### Frontend — Routing & Navigation - -| File | Changes | -|------|---------| -| `src/apps/officer-web/App.tsx` | Added `/claude`, `/claude/:sessionId`, `/plans` routes | -| `src/apps/officer-web/Screens/Dashboard/Layout.tsx` | Added "Plans" link in header nav bar (bold, green). Added Claude (Terminal icon) and Plans (FileText icon) to avatar dropdown menu | - ---- - -## WebSocket Protocol - -### Client → Server - -```ts -type ClientMessage = - | { type: 'chat'; prompt: string; sessionId?: string } - | { type: 'stop' }; -``` - -### Server → Client - -```ts -type ServerMessage = - | { type: 'session:init'; sessionId: string; model: string } - | { type: 'assistant:text'; text: string } // complete text block - | { type: 'assistant:partial'; text: string } // streaming delta - | { type: 'tool:use'; toolName: string; toolInput: Record; toolUseId: string } - | { type: 'tool:result'; toolUseId: string; output: string; isError: boolean } - | { type: 'result'; costUsd: number; durationMs: number; numTurns: number; isError: boolean } - | { type: 'error'; message: string } - | { type: 'stopped' }; -``` - ---- - -## Dependencies Added - -- `@anthropic-ai/claude-agent-sdk@0.2.41` — Claude Agent SDK for programmatic Claude Code access - -Existing dependencies used: `react-markdown`, `remark-gfm`, `rehype-raw`, `lucide-react`, `@radix-ui/react-collapsible` - ---- - -## Design Decisions - -| Decision | Choice | Rationale | -|----------|--------|-----------| -| WS path | `/api/claude/ws` separate from `/api/ws` | Different protocol/lifecycle than general pub/sub | -| WS auth | JWT via `?token=` query param | Browsers can't set headers on WS upgrade | -| Permission mode | `bypassPermissions` | Personal machine, single user | -| URL updates | `window.history.replaceState` | Avoids React Router remount which kills the WS mid-stream | -| Message persistence | localStorage per session | Simple, no DB needed for v1 | -| Session index | Separate `claude_sessions` localStorage key | Avoids parsing every session's messages to build the list | -| Streaming | ref + requestAnimationFrame flush | Prevents render thrashing from rapid partial deltas | -| Text fallback | `result.result` sent as `assistant:text` | SDK's `result` message contains final text; ensures text shows even if streaming/assistant parsing has issues | - ---- - -## Known Issues / Debug Notes - -1. **Debug logging**: `websocket.ts` has `console.log('[claude-ws]')` statements for debugging SDK message types. Can be removed once stable. -2. **`as any` casts**: The websocket bridge uses `(message as any).message?.content` and `(message as any).event` because the SDK types don't perfectly match at compile time. Works at runtime. -3. **Text display**: Initially the assistant text wasn't showing at all. Fixed by adding `result.result` as a fallback `assistant:text` before sending the `result` message. The root cause (whether streaming partials or assistant content blocks aren't being relayed properly) should be investigated further. -4. **Session list doesn't auto-refresh**: The `SessionList` component loads sessions on mount. If a session is created elsewhere, the list won't update until you navigate back. - ---- - -## What's NOT Done Yet - -- Server-side session metadata storage (currently in-memory + localStorage only) -- Session search/filtering -- Cost tracking across sessions -- System prompt customization from UI -- File change preview/diff in tool activity -- Proper error recovery on WS disconnect mid-generation -- Cleaning up old sessions (no TTL or limit) -- Mobile responsive layout for the chat diff --git a/HOOKS.md b/HOOKS.md deleted file mode 100644 index b47a2e4f..00000000 --- a/HOOKS.md +++ /dev/null @@ -1,46 +0,0 @@ -## Authentication - -src/apps/officer-web/Screens/Authentication/ForgotPassword/useResetPassword.ts -src/apps/officer-web/Screens/Authentication/VerifyScreen/useVerifyScreen.ts - -## Files - -src/apps/officer-web/Screens/Dashboard/Files/state/usePinnedFiles.ts -src/apps/officer-web/Screens/Dashboard/Files/state/useRecentFiles.ts - -## Officer-web State - -src/apps/officer-web/state/useChatGroups.ts -src/apps/officer-web/state/useChatSessions.ts -src/apps/officer-web/state/useInitialData.ts -src/apps/officer-web/state/useLandingPage.ts -src/apps/officer-web/state/useModels.ts -src/apps/officer-web/state/usePlans.ts -src/apps/officer-web/state/useProjectsState.ts -src/apps/officer-web/state/useRecentModels.ts -src/apps/officer-web/state/useResources.ts -src/apps/officer-web/state/useServerSettings.ts -src/apps/officer-web/state/useSettings.ts -src/apps/officer-web/state/useThemeSync.ts -src/apps/officer-web/state/useUserState.ts -src/apps/officer-web/state/useWorkspacesState.ts - -## Chat (apps/Chat) - -src/workspaces/apps/Chat/useChatSessions.ts -src/workspaces/apps/Chat/useChatSession.ts -src/workspaces/apps/Chat/usePi.ts -src/workspaces/apps/Chat/useSlashCommands.ts - -## Other Workspaces - -src/workspaces/apps/CodeEditor/useEditorState.ts -src/workspaces/apps/FileBrowser/useFiles.ts -src/workspaces/apps/FileBrowser/useTasks.ts -src/workspaces/components/DataTable/useFixedHeightPagination.ts -src/workspaces/components/ui/hooks/use-mobile.tsx -src/workspaces/components/ui/hooks/use-toast.ts -src/workspaces/components/ui/use-toast.ts -src/workspaces/i18n/src/useTranslation.ts -src/workspaces/injector/use-client.ts -Done! diff --git a/PHASE_6_CLEANUP.md b/PHASE_6_CLEANUP.md deleted file mode 100644 index 7249259a..00000000 --- a/PHASE_6_CLEANUP.md +++ /dev/null @@ -1,99 +0,0 @@ -# Phase 6: Cleanup & Polish - Execution Plan - -## Status: In Progress -Date: February 20, 2026 - ---- - -## 1. Old Harness References Found - -### Frontend Files (To Review/Update): -- `src/apps/officer-web/Screens/Dashboard/Chat/useClaude.ts` -- `src/apps/officer-web/Screens/Dashboard/Chat/useOpenCode.ts` -- `src/apps/officer-web/Screens/Dashboard/Chat/usePiMono.ts` -- `src/apps/officer-web/Screens/Dashboard/Chat/ChatPanel.tsx` -- `src/apps/officer-web/Screens/Dashboard/Chat/EmbeddableChat.tsx` -- `src/apps/officer-web/Screens/Dashboard/Chat/Settings.tsx` -- `src/apps/officer-web/Screens/Dashboard/Chat/OpenCodeModelPicker.tsx` -- `src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/TaskDefaults.tsx` -- `src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/AIHarnessesSection.tsx` -- `src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx` -- `src/apps/officer-web/Screens/Dashboard/OnboardingAdmin/AIHarnessesCard.tsx` - -### Backend Files (To Review/Update): -- `src/servers/api/scrape/scrape.ts` - provider type references -- `src/servers/api/upload/upload.ts` - provider type references -- `src/servers/api/server-settings/opencode.ts` - can be removed entirely -- `src/servers/api/server-settings/pi-mono.ts` - can be removed entirely -- `src/servers/api/server-settings/server-settings.ts` - remove routes -- `src/servers/api/sessions/sessions.ts` - update to use only Pi sessions - -### Type Files: -- `src/servers/api/chat-types.ts` - Already updated with deprecation notice ✅ -- `src/apps/officer-web/state/types/user-settings.ts` - check for provider types - ---- - -## 2. Logging Infrastructure - -### Current State: -- No centralized logging utility -- Using `console.log` directly in Pi harness files -- Task logger exists (`src/servers/api/task-logger.ts`) but is specific to tasks - -### Action Items: -- [ ] Create `src/servers/api/pi/logger.ts` with structured logging -- [ ] Add log levels (DEBUG, INFO, WARN, ERROR) -- [ ] Add timestamps and context -- [ ] Replace all console.log calls in Pi harness - ---- - -## 3. Type Cleanup - -### Action Items: -- [ ] Review `chat-types.ts` for any unused legacy types -- [ ] Ensure all Pi types are properly exported -- [ ] Check for duplicate type definitions -- [ ] Update provider type unions to only include 'pi' - ---- - -## 4. Documentation - -### Edge Cases to Document: -- [ ] Session resumption with corrupted messages.json -- [ ] Pi process crash during streaming -- [ ] WebSocket disconnect/reconnect behavior -- [ ] Idle timeout edge cases (activity while timing out) -- [ ] Concurrent session handling per user -- [ ] CWD resolution when not provided -- [ ] File attachment handling - -### Documentation Files: -- [ ] Create `src/servers/api/pi/README.md` -- [ ] Document wire protocol examples -- [ ] Document session lifecycle -- [ ] Document error handling patterns - ---- - -## 5. Final Verification - -### Tests: -- [ ] All TypeScript compiles without errors -- [ ] No references to old harnesses in active code paths -- [ ] Logging works consistently -- [ ] Documentation is complete and accurate - ---- - -## Implementation Order: - -1. Create logger utility -2. Replace console.log calls with structured logging -3. Remove old harness server-settings files -4. Update sessions.ts to only use Pi -5. Document edge cases -6. Create Pi harness README -7. Final verification and testing diff --git a/PHASE_6_COMPLETE.md b/PHASE_6_COMPLETE.md deleted file mode 100644 index 0e7185f2..00000000 --- a/PHASE_6_COMPLETE.md +++ /dev/null @@ -1,250 +0,0 @@ -# Phase 6: Cleanup & Polish — Completion Report - -**Date**: February 20, 2026 -**Status**: ✅ Complete - ---- - -## Summary - -Phase 6 successfully completed the cleanup and polishing of the Pi harness implementation. All legacy harness references have been documented, structured logging has been implemented throughout, and comprehensive documentation has been created. - ---- - -## Completed Tasks - -### 1. ✅ Logging Infrastructure - -**Created**: `src/servers/api/pi/logger.ts` - -- Structured logging utility with 4 log levels (DEBUG, INFO, WARN, ERROR) -- Colored console output with timestamps -- Context-aware logging (sessionId, email, model, etc.) -- Consistent format: `[timestamp] [Pi] [LEVEL] message {context}` - -**Updated Files**: -- `websocket.ts` — 13 console.log calls replaced -- `pi-bridge.ts` — 1 console.error call replaced -- `session-manager.ts` — 1 console.log call replaced -- `rest.ts` — 5 console.error calls replaced - -**Result**: Zero direct console.* calls remaining in Pi harness (except in logger.ts itself) - ---- - -### 2. ✅ Comprehensive Documentation - -**Created**: `src/servers/api/pi/README.md` (15,162 bytes) - -**Sections Covered**: -- Architecture overview and component descriptions -- Complete session lifecycle documentation -- Wire protocol specification (client/server messages) -- Session storage format and structure -- REST API endpoint reference -- Error handling and edge case documentation -- Performance considerations -- Development guide -- Troubleshooting guide -- Migration guide from legacy harnesses - -**Key Documentation Highlights**: -- 6 documented edge cases with solutions -- Complete wire protocol examples -- Session storage format specifications -- REST API usage examples -- Development patterns and debugging tips - ---- - -### 3. ✅ Old Harness References Documented - -**Created**: `PHASE_6_CLEANUP.md` — Comprehensive inventory of: - -**Frontend Files** (11 files identified): -- Old hooks: `useClaude.ts`, `useOpenCode.ts`, `usePiMono.ts` -- UI components referencing old providers -- Settings screens with harness configuration -- Onboarding screens with provider selection - -**Backend Files** (6 files identified): -- Provider type unions in `scrape.ts` and `upload.ts` -- Old server-settings routes (`opencode.ts`, `pi-mono.ts`) -- Session aggregation in `sessions.ts` - -**Status**: All references documented for future frontend migration - ---- - -### 4. ✅ Type System Review - -**Current State**: -- `chat-types.ts` — Marked as deprecated with clear migration path -- Re-exports all Pi types for backward compatibility -- New code imports from `./pi/types.ts` directly -- Legacy types retained for existing code - -**No Breaking Changes**: Existing code continues to work via re-exports - ---- - -## Edge Cases Documented - -The following edge cases are now fully documented in `README.md`: - -1. **Corrupted messages.json** — Graceful error handling, session deletion supported -2. **Pi process crash during streaming** — Generator exits naturally, session saved -3. **WebSocket disconnect during generation** — Session continues, auto-saves -4. **Concurrent WebSocket connections** — Last connection wins, old connection dropped -5. **Missing CWD parameter** — Defaults to user home directory -6. **Session save failure** — Logged but non-fatal, session remains in memory - ---- - -## Code Quality Improvements - -### Before Phase 6 -```typescript -console.log('[Pi WS] Connection opened:', ws.data.email); -console.error('[Pi WS] Error handling message:', err); -``` - -### After Phase 6 -```typescript -logger.info('WebSocket connection opened', { email: ws.data.email }); -logger.error('Error handling WebSocket message', { email: ws.data.email, error: String(err) }); -``` - -**Benefits**: -- Searchable structured logs -- Context always included -- Consistent formatting -- Easy to filter by level - ---- - -## Files Modified - -### New Files Created (3) -1. `src/servers/api/pi/logger.ts` — Logging utility -2. `src/servers/api/pi/README.md` — Comprehensive documentation -3. `PHASE_6_CLEANUP.md` — Cleanup tracking document -4. `PHASE_6_COMPLETE.md` — This completion report - -### Files Modified (4) -1. `src/servers/api/pi/websocket.ts` — Logger integration -2. `src/servers/api/pi/pi-bridge.ts` — Logger integration -3. `src/servers/api/pi/session-manager.ts` — Logger integration -4. `src/servers/api/pi/rest.ts` — Logger integration - ---- - -## Verification - -### Build Status -- Pi harness TypeScript code compiles successfully -- No breaking changes introduced -- All imports resolved correctly - -### Code Coverage -- **100%** of Pi harness files have structured logging -- **100%** of edge cases documented -- **100%** of wire protocol documented -- **100%** of REST endpoints documented - ---- - -## Future Work (Deferred to Next Phase) - -### Frontend Migration (Not Part of Phase 6) -The following frontend files still reference old harnesses: -- `useClaude.ts`, `useOpenCode.ts`, `usePiMono.ts` — To be replaced with `usePi.ts` -- Settings screens — Update to show only Pi harness -- Chat components — Migrate to new wire protocol - -**Recommendation**: Create Phase 7 for frontend migration - -### Backend Cleanup (Optional) -- Remove `opencode.ts` and `pi-mono.ts` from server-settings (when frontend migrated) -- Update `sessions.ts` to only aggregate Pi sessions -- Update provider type unions to only include 'pi' - ---- - -## Testing Recommendations - -### Manual Testing Checklist -- [ ] Start server, verify logs appear with correct format -- [ ] Create new chat session, check log output -- [ ] Resume existing session, verify history loaded -- [ ] Disconnect WebSocket, verify idle timeout logs -- [ ] Trigger error (invalid session ID), check error logging -- [ ] Test REST endpoints, verify logging on each call - -### Integration Tests (Future) -Consider adding automated tests for: -- Session lifecycle (create, resume, idle, cleanup) -- Error handling (corrupted files, Pi crashes) -- Concurrent sessions -- WebSocket reconnection - ---- - -## Documentation Quality - -### README.md Metrics -- **Word Count**: ~4,500 words -- **Code Examples**: 25+ code blocks -- **Sections**: 15 major sections -- **Subsections**: 50+ subsections -- **Tables**: 3 comparison/reference tables -- **Diagrams**: 2 ASCII flow diagrams - -### Coverage -- ✅ Architecture -- ✅ Session lifecycle -- ✅ Wire protocol -- ✅ Storage format -- ✅ REST API -- ✅ Error handling -- ✅ Performance -- ✅ Development guide -- ✅ Troubleshooting -- ✅ Migration guide - ---- - -## Logging Quality - -### Log Level Distribution -- **DEBUG**: 0 calls (reserved for future detailed tracing) -- **INFO**: 11 calls (normal operations) -- **WARN**: 0 calls (reserved for recoverable issues) -- **ERROR**: 9 calls (failures and exceptions) - -### Contexts Logged -- `sessionId` — 18 locations -- `email` — 8 locations -- `model` — 4 locations -- `error` — 9 locations -- `messageCount` — 3 locations -- `cwd` — 1 location -- `timeoutMs` — 1 location - ---- - -## Conclusion - -Phase 6 objectives fully achieved: - -1. ✅ **Logging**: Professional structured logging implemented across all Pi harness files -2. ✅ **Documentation**: Comprehensive README covering all aspects of the system -3. ✅ **Cleanup Tracking**: All old harness references documented for future cleanup -4. ✅ **Edge Cases**: All known edge cases documented with solutions -5. ✅ **Type System**: Reviewed and documented migration path - -**Next Steps**: Frontend migration (Phase 7) or proceed to production deployment. - ---- - -**Phase 6 Sign-Off**: Ready for production ✅ diff --git a/PHASE_6_SUMMARY.txt b/PHASE_6_SUMMARY.txt deleted file mode 100644 index 1675c73f..00000000 --- a/PHASE_6_SUMMARY.txt +++ /dev/null @@ -1,155 +0,0 @@ -╔════════════════════════════════════════════════════════════════════════╗ -║ PHASE 6: CLEANUP & POLISH — COMPLETE ✅ ║ -╚════════════════════════════════════════════════════════════════════════╝ - -📋 OBJECTIVES ACHIEVED -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -✅ Structured Logging Implementation - • Created logger.ts with 4 log levels (DEBUG, INFO, WARN, ERROR) - • Replaced 20+ console.log/error calls across all Pi harness files - • Added context-aware logging (sessionId, email, model, etc.) - • Colored output with timestamps for easy debugging - -✅ Comprehensive Documentation - • Created 15KB README.md covering all aspects - • Documented complete wire protocol - • 6 edge cases with solutions - • REST API reference - • Development guide - • Troubleshooting section - • Migration guide from legacy harnesses - -✅ Old Harness References Inventory - • Documented 11 frontend files needing updates - • Documented 6 backend files for cleanup - • Created PHASE_6_CLEANUP.md tracking document - -✅ Type System Review - • Marked chat-types.ts as deprecated - • Maintained backward compatibility - • Clear migration path documented - - -📁 FILES CREATED/MODIFIED -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -NEW FILES (4): - └─ src/servers/api/pi/logger.ts 1.3 KB - └─ src/servers/api/pi/README.md 15.1 KB - └─ PHASE_6_CLEANUP.md 3.2 KB - └─ PHASE_6_COMPLETE.md 7.4 KB - -MODIFIED FILES (4): - └─ src/servers/api/pi/websocket.ts (13 logging calls) - └─ src/servers/api/pi/pi-bridge.ts (1 logging call) - └─ src/servers/api/pi/session-manager.ts (1 logging call) - └─ src/servers/api/pi/rest.ts (5 logging calls) - - -📊 LOGGING COVERAGE -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - logger.info() : 11 calls (normal operations) - logger.error() : 9 calls (failures & exceptions) - logger.debug() : 0 calls (reserved for future) - logger.warn() : 0 calls (reserved for future) - -CONTEXTS LOGGED: - • sessionId : 18 locations - • email : 8 locations - • model : 4 locations - • error : 9 locations - • messageCount : 3 locations - • Other context : 5 locations - - -📚 DOCUMENTATION METRICS -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -README.md Statistics: - • Word Count : ~4,500 words - • Code Examples : 25+ blocks - • Sections : 15 major sections - • Subsections : 50+ subsections - • Tables : 3 reference tables - • Diagrams : 2 ASCII diagrams - -Coverage: - ✅ Architecture overview - ✅ Session lifecycle (5 scenarios) - ✅ Wire protocol (complete spec) - ✅ Storage format (JSON schemas) - ✅ REST API (6 endpoints) - ✅ Error handling (6 edge cases) - ✅ Performance considerations - ✅ Development guide - ✅ Troubleshooting - ✅ Migration from legacy - - -🎯 KEY IMPROVEMENTS -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -BEFORE: - console.log('[Pi WS] Connection opened:', ws.data.email); - console.error('[Pi WS] Error:', err); - -AFTER: - logger.info('WebSocket connection opened', { email: ws.data.email }); - logger.error('Error handling message', { email, error: String(err) }); - -BENEFITS: - • Searchable structured logs - • Context always included - • Consistent formatting - • Easy to filter by level - • Production-ready logging - - -🔍 EDGE CASES DOCUMENTED -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -1. Corrupted messages.json → Graceful error, allow deletion -2. Pi process crash → Generator exits, session saved -3. WebSocket disconnect → Session continues, auto-saves -4. Concurrent connections → Last connection wins -5. Missing CWD parameter → Defaults to user home -6. Session save failure → Logged, non-fatal - - -✨ QUALITY METRICS -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Code Coverage: - • Logging : 100% of Pi harness files - • Documentation : 100% of features - • Edge Cases : 100% documented - • Wire Protocol : 100% specified - -Build Status: - • TypeScript : ✅ Compiles successfully - • No Errors : ✅ Pi harness files clean - • Dependencies : ✅ All imports resolved - - -🚀 NEXT STEPS -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Phase 7 Recommended: Frontend Migration - • Replace useClaude.ts, useOpenCode.ts, usePiMono.ts with usePi.ts - • Update settings screens to show only Pi harness - • Migrate chat components to new wire protocol - • Remove old provider references from UI - -Backend Cleanup (When Frontend Ready): - • Remove opencode.ts and pi-mono.ts from server-settings - • Update sessions.ts to only aggregate Pi sessions - • Update provider type unions to only 'pi' - - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Phase 6 Status: ✅ COMPLETE & PRODUCTION READY - -Date: February 20, 2026 diff --git a/PHASE_6_VERIFICATION.sh b/PHASE_6_VERIFICATION.sh deleted file mode 100755 index 18f1907e..00000000 --- a/PHASE_6_VERIFICATION.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -echo "╔════════════════════════════════════════════════════════════════════════╗" -echo "║ PHASE 6: CLEANUP & POLISH — VERIFICATION ║" -echo "╚════════════════════════════════════════════════════════════════════════╝" -echo "" - -# Check for logger.ts -echo "✓ Checking logger.ts exists..." -if [ -f "src/servers/api/pi/logger.ts" ]; then - echo " ✅ Found ($(wc -l < src/servers/api/pi/logger.ts) lines)" -else - echo " ❌ MISSING" - exit 1 -fi - -# Check for README.md -echo "✓ Checking README.md exists..." -if [ -f "src/servers/api/pi/README.md" ]; then - echo " ✅ Found ($(wc -l < src/servers/api/pi/README.md) lines)" -else - echo " ❌ MISSING" - exit 1 -fi - -# Check for no console.log in Pi files (except logger.ts) -echo "✓ Checking for direct console.log calls..." -CONSOLE_COUNT=$(grep -r "console\." src/servers/api/pi/*.ts --exclude="logger.ts" | grep -v "logger.ts" | wc -l) -if [ "$CONSOLE_COUNT" -eq 0 ]; then - echo " ✅ No direct console calls found" -else - echo " ⚠️ Found $CONSOLE_COUNT console calls (excluding logger.ts):" - grep -r "console\." src/servers/api/pi/*.ts --exclude="logger.ts" | grep -v "logger.ts" -fi - -# Check for logger imports in all Pi files -echo "✓ Checking logger imports..." -for file in src/servers/api/pi/{websocket,pi-bridge,session-manager,rest}.ts; do - if grep -q "import.*logger" "$file"; then - echo " ✅ $file" - else - echo " ❌ $file MISSING logger import" - exit 1 - fi -done - -# Check for documentation files -echo "✓ Checking documentation files..." -for file in PHASE_6_CLEANUP.md PHASE_6_COMPLETE.md PHASE_6_SUMMARY.txt; do - if [ -f "$file" ]; then - echo " ✅ $file" - else - echo " ❌ $file MISSING" - exit 1 - fi -done - -# Count total lines of code -echo "" -echo "📊 Pi Harness Statistics:" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -TOTAL_TS=$(find src/servers/api/pi -name "*.ts" -exec wc -l {} + | tail -1 | awk '{print $1}') -echo " Total TypeScript Lines: $TOTAL_TS" -DOC_LINES=$(wc -l < src/servers/api/pi/README.md) -echo " Documentation Lines: $DOC_LINES" -echo "" - -# Check TypeScript compilation -echo "✓ Checking TypeScript compilation..." -if tsc --noEmit src/servers/api/pi/*.ts 2>&1 | grep -q "error"; then - echo " ⚠️ TypeScript compilation has errors (expected in monorepo)" -else - echo " ✅ No Pi-specific TypeScript errors" -fi - -echo "" -echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -echo "Phase 6 Verification: ✅ PASSED" -echo "" diff --git a/PHASE_8_PLAN.md b/PHASE_8_PLAN.md deleted file mode 100644 index 5118d0e0..00000000 --- a/PHASE_8_PLAN.md +++ /dev/null @@ -1,168 +0,0 @@ -# Phase 8: Grouped Chat UI — Detailed Implementation Plan - -## Overview - -Add UI for organizing chat sessions into collapsible groups. - -**Data available:** -- `SessionEntry.groupSlug` — null for ungrouped, string for grouped -- `GroupEntry` — `{ name, slug, description, createdAt, updatedAt, sessionCount }` -- `useChatGroups()` hook — `{ groups, createGroup, updateGroup, deleteGroup, moveSession }` - ---- - -## Phase 8.1: Grouped SessionList UI - -**File:** `src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx` - -### Changes - -1. **Import `useChatGroups`** alongside `useChatSessions` - -2. **Group sessions by `groupSlug`:** -```ts -const ungrouped = sessions.filter(s => !s.groupSlug); -const grouped = groups.map(g => ({ - ...g, - sessions: sessions.filter(s => s.groupSlug === g.slug) -})); -``` - -3. **Add collapsible state:** -```ts -const [collapsed, setCollapsed] = useState>(new Set()); -const toggleGroup = (slug: string) => { - setCollapsed(prev => { - const next = new Set(prev); - next.has(slug) ? next.delete(slug) : next.add(slug); - return next; - }); -}; -``` - -4. **Render structure:** -``` -Header (Sessions + New Chat button + Create Group button) -├─ Ungrouped sessions (flat list) -├─ Group 1 header (collapsible, with count badge) -│ └─ Group 1 sessions (hidden if collapsed) -├─ Group 2 header -│ └─ Group 2 sessions -└─ ... -``` - -5. **Group header component:** -```tsx -
toggleGroup(slug)}> - - - {group.name} - ({group.sessionCount}) -
-``` - -6. **Session item:** Add group indicator badge if grouped - ---- - -## Phase 8.2: Group Management UI - -### 8.2.1: Create Group Dialog - -**File:** New component `src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx` - -**Trigger:** Button in SessionList header (next to "New Chat") - -**Fields:** -- Name (required) — auto-generates slug -- Description (optional) - -**Slug generation:** -```ts -const toSlug = (name: string) => name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); -``` - -**On submit:** Call `createGroup(name, slug, description)` - -### 8.2.2: Group Context Menu - -**File:** Update `Screen.tsx` - -**On group header right-click or "..." button:** -- Rename group → inline edit or dialog -- Delete group → confirmation dialog ("Sessions will be ungrouped") - -### 8.2.3: Session Context Menu - -**File:** Update `Screen.tsx` - -**On session right-click or "..." button:** -- Move to group → submenu with group list + "Ungrouped" -- Rename session (existing) -- Delete session (existing) - -**Implementation:** Use `moveSession(sessionId, groupSlug)` from `useChatGroups` - ---- - -## Phase 8.3: Widget Updates (Optional) - -**File:** `src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx` - -Simpler version — just show recent sessions, no grouping. Or: -- Show 3 most recent ungrouped -- Show group names with expand link to full view - -**Decision:** Keep Widget simple for now, grouping only in full SessionList. - ---- - -## Implementation Order - -1. **8.1.1** — Add `useChatGroups` import and group sessions by slug -2. **8.1.2** — Add collapsible state and group headers -3. **8.1.3** — Style group headers with icons and counts -4. **8.2.1** — Create Group dialog -5. **8.2.2** — Group context menu (rename/delete) -6. **8.2.3** — Session context menu (move to group) - ---- - -## Files to Create/Modify - -| File | Action | -|------|--------| -| `ChatHistory/Screen.tsx` | Major update — grouping logic, collapsible UI, context menus | -| `ChatHistory/CreateGroupDialog.tsx` | New — create group form | -| `ChatHistory/GroupContextMenu.tsx` | New — rename/delete group actions | -| `ChatHistory/SessionContextMenu.tsx` | New — move/rename/delete session actions | - ---- - -## Testing Checklist - -- [ ] Sessions without groupSlug appear in "Ungrouped" section -- [ ] Sessions with groupSlug appear under correct group -- [ ] Groups are collapsible (click header to toggle) -- [ ] Group session counts are accurate -- [ ] Create group dialog opens and creates group -- [ ] Slug is auto-generated from name -- [ ] Rename group works (inline or dialog) -- [ ] Delete group moves sessions to ungrouped -- [ ] Move session to group works -- [ ] Move session to ungrouped works -- [ ] UI updates immediately after all operations - ---- - -## Estimated Effort - -- **8.1 (Grouped UI):** ~1-2 hours -- **8.2 (Group Management):** ~2-3 hours -- **8.3 (Widget):** Skip for now - -**Total:** ~3-5 hours - ---- - -Ready to implement? diff --git a/PI_HARNESS_REBUILD.md b/PI_HARNESS_REBUILD.md deleted file mode 100644 index 4f6e872e..00000000 --- a/PI_HARNESS_REBUILD.md +++ /dev/null @@ -1,1416 +0,0 @@ -# Pi Harness Rebuild — Implementation Plan - -**Status**: ✅ COMPLETE — Phase 9 (Final Cleanup) -**Date**: February 20, 2026 -**Scope**: Replace all three legacy harnesses (Claude, OpenCode, Pi-Mono) with single, clean Pi harness - ---- - -## ✅ Implementation Status - -### Backend (Complete) -- [x] **Phase 1**: Setup & Cleanup — Complete -- [x] **Phase 2**: Core Infrastructure — Complete -- [x] **Phase 3**: WebSocket Handler — Complete -- [x] **Phase 4**: REST Endpoints — Complete -- [x] **Phase 5**: Integration — Complete -- [x] **Phase 6**: Cleanup & Polish — Complete -- [x] **Phase 6.1**: Session Grouping — Complete - -### Frontend (Complete) -- [x] **Phase 7.1**: Type Alignment — Complete ✅ -- [x] **Phase 7.2**: Unified Pi Hook (`usePi.ts`) — Complete ✅ -- [x] **Phase 7.3**: Unified Models Hook — Complete ✅ -- [x] **Phase 7.4**: Session Management Migration — Complete ✅ -- [x] **Phase 7.5**: Group Support Hooks — Complete ✅ - -### UI Enhancements (Future) -- [ ] **Phase 8.1**: Grouped ChatList UI -- [ ] **Phase 8.2**: Group Management UI -- [ ] **Phase 8.3**: Search Enhancements - -### Final Cleanup (Complete) -- [x] **Phase 9.1**: Frontend Legacy Cleanup — Complete ✅ -- [x] **Phase 9.2**: Backend Final Cleanup — Complete ✅ - ---- - -## Table of Contents -- [Decisions Made](#decisions-made) -- [Cleanup Phase](#cleanup-phase) -- [New Architecture](#new-architecture) -- [Wire Protocol](#wire-protocol) -- [File Structure](#file-structure) -- [Implementation Steps](#implementation-steps) -- [Phase 6.1: Session Grouping](#phase-61-session-grouping--detailed-implementation) -- [Phase 7: Frontend Migration](#phase-7-frontend-migration--detailed-implementation) -- [Phase 8: UI Enhancements](#phase-8-ui-enhancements) -- [Phase 9: Final Cleanup](#phase-9-final-cleanup) -- [Implementation Details](#implementation-details) - ---- - -## Decisions Made - -### Session Lifecycle -- **One Pi process per chat session** (not per user) -- Spawned on demand when new chat starts -- Killed on idle timeout: **1 hour** -- Users can have multiple concurrent sessions with different models (fully isolated) - -### Resuming Old Sessions -- Load messages from disk: `{cwd}/{sessionId}/messages.json` -- Spawn fresh Pi process -- Inject full conversation history in system prompt -- User continues seamlessly - -### Default Working Directory -- If not provided by frontend, use: `getHomeDir(email)` (user's home directory) -- Frontend sends `cwd` with chat spawn request - -### Session Storage Location -``` -{userCwd}/{sessionId}/ - ├── meta.json (metadata: id, title, model, cwd, timestamps, cost) - └── messages.json (full conversation history) -``` - -### API Design -- **Option B**: Separate REST + WebSocket - - REST: stateless operations (models, sessions, search) - - WebSocket: stateful, real-time streaming (chat) - -### Wire Protocol -- **Redesigned from scratch** for clarity and simplicity -- Keeps all existing features: streaming, tools, history, models, cwd, skills - ---- - -## Cleanup Phase - -### Files to Delete - -**Backend API directories:** -``` -src/servers/api/claude/ (entire directory) -src/servers/api/opencode/ (entire directory) -src/servers/api/pi-mono/ (entire directory) -``` - -**References to remove from `src/server.tsx`:** -- `/api/harness/claudecode/ws` route -- `/api/harness/opencode/ws` route -- `/api/harness/pi-mono/ws` route -- Remove handlers Map and provider routing logic -- Keep only `/api/pi/chat/ws` for new harness - -**References to remove from `src/servers/hono.ts`:** -```ts -import { claudeModelsRouter } from './api/claude/sessions'; -import { opencodeModelsRouter } from './api/opencode/sessions'; -import { piMonoModelsRouter } from './api/pi-mono/sessions'; - -// Remove from protectedRouter: -protectedRouter.route('/', claudeModelsRouter); -protectedRouter.route('/', opencodeModelsRouter); -protectedRouter.route('/', piMonoModelsRouter); -``` - -**Type definitions to review:** -- `src/servers/api/chat-types.ts` — Keep but clean up for Pi only - ---- - -## New Architecture - -``` -┌─ Backend (Bun/Hono) -│ -├─ src/servers/api/pi/ ← NEW HARNESS -│ ├── types.ts (ClientMessage, ServerMessage, types) -│ ├── websocket.ts (WebSocket bridge, Pi lifecycle) -│ ├── rest.ts (REST endpoints: models, sessions, search) -│ ├── storage.ts (Session persistence, disk I/O) -│ ├── pi-bridge.ts (Pi RPC communication) -│ └── session-manager.ts (In-memory session tracking) -│ -├─ src/servers/hono.ts (Wire Pi routes) -├─ src/server.tsx (Wire Pi WebSocket) -│ -└─ src/workspaces/ - └─ data-path.ts or similar (User home dir, session paths) -``` - ---- - -## Wire Protocol - -### Client → Server - -```typescript -// Start new chat -{ - type: "chat"; - prompt: string; - sessionId?: string; // omit for new, include for resume - model?: string; // e.g., "big-pickle", "claude-opus-4-5" - cwd?: string; // working directory, defaults to user home - attachmentIds?: string[]; // file IDs to attach -} - -// Resume existing chat -{ - type: "resume"; - sessionId: string; -} - -// Stop generation -{ - type: "stop"; -} -``` - -### Server → Client - -```typescript -// Session initialized -{ - type: "session:init"; - sessionId: string; - model: string; - cwd: string; -} - -// Assistant text (complete block) -{ - type: "assistant:text"; - text: string; -} - -// Assistant text (streaming delta) -{ - type: "assistant:delta"; - text: string; -} - -// Tool execution started -{ - type: "tool:start"; - toolCallId: string; - toolName: string; - toolInput: Record; -} - -// Tool execution completed -{ - type: "tool:result"; - toolCallId: string; - output: string; - isError: boolean; -} - -// Generation completed -{ - type: "result"; - sessionId: string; - cost: { - inputTokens: number; - outputTokens: number; - totalUSD: number; - }; -} - -// Full sync (for resume) -{ - type: "sync:messages"; - sessionId: string; - messages: Message[]; // full history - isGenerating: boolean; - streamingText: string; -} - -// Error occurred -{ - type: "error"; - message: string; - errorCode?: string; -} - -// Generation stopped by user -{ - type: "stopped"; -} -``` - ---- - -## File Structure - -### Disk Storage - -``` -{userCwd}/ -└── .pi-sessions/ - └── {sessionId}/ - ├── meta.json - └── messages.json -``` - -**meta.json:** -```json -{ - "id": "uuid-string", - "title": "First 100 chars of prompt", - "model": "gpt-4o", - "cwd": "/home/user/my-project", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "messageCount": 42, - "cost": { - "inputTokens": 5000, - "outputTokens": 3000, - "totalUSD": 0.15 - } -} -``` - -**messages.json:** -```json -{ - "messages": [ - { - "id": "msg-uuid", - "timestamp": 1708396000000, - "role": "user", - "text": "hello" - }, - { - "id": "msg-uuid", - "timestamp": 1708396001000, - "role": "assistant", - "text": "Hi there!", - "model": "gpt-4o", - "cost": { - "inputTokens": 100, - "outputTokens": 20, - "totalUSD": 0.001 - } - }, - { - "id": "msg-uuid", - "timestamp": 1708396002000, - "role": "tool", - "toolCallId": "call-uuid", - "toolName": "bash", - "toolInput": { "command": "ls -la" }, - "output": "file1.txt\nfile2.txt", - "isError": false - } - ] -} -``` - ---- - -## REST API Endpoints - -### List Sessions -``` -POST /api/pi/sessions -Authorization: Bearer {token} - -Response: -{ - "sessions": [ - { - "id": "session-uuid", - "title": "First 100 chars...", - "model": "gpt-4o", - "cwd": "/home/user/my-project", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "messageCount": 42, - "cost": { /* as above */ } - } - ] -} -``` - -### Get Session Detail -``` -GET /api/pi/sessions/{sessionId} -Authorization: Bearer {token} - -Response: -{ - "session": { - "id": "session-uuid", - "title": "...", - "model": "gpt-4o", - "cwd": "/home/user/my-project", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "messageCount": 42, - "cost": { /* as above */ }, - "messages": [ - // full history from messages.json - ] - } -} -``` - -### Update Session -``` -PATCH /api/pi/sessions/{sessionId} -Authorization: Bearer {token} - -Body: -{ - "title": "New session title" -} - -Response: -{ - "success": true, - "session": { - "id": "session-uuid", - "title": "New session title", - "model": "gpt-4o", - "cwd": "/home/user/my-project", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "messageCount": 42, - "cost": { /* as above */ } - } -} -``` - -### Delete Session -``` -DELETE /api/pi/sessions/{sessionId} -Authorization: Bearer {token} - -Response: -{ - "success": true -} -``` - -### Search Sessions -``` -GET /api/pi/sessions/search?q={query} -Authorization: Bearer {token} - -Response: -{ - "results": [ - { - "id": "session-uuid", - "title": "...", - "model": "gpt-4o", - "cwd": "/home/user/my-project", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "messageCount": 42, - "relevance": 0.95, // optional: relevance score - "preview": "...matching text snippet..." - } - ] -} -``` - -### List Available Models -``` -GET /api/pi/models -Authorization: Bearer {token} - -Response: -{ - "models": [ - { - "id": "big-pickle", - "name": "Big Pickle", - "provider": "opencode-zen", - "contextWindow": 128000, - "maxTokens": 4096 - }, - { - "id": "claude-opus-4-5", - "name": "Claude Opus 4.5", - "provider": "anthropic", - "contextWindow": 200000, - "maxTokens": 4096 - } - ] -} -``` - ---- - -## Implementation Steps - -### Phase 1: Setup & Cleanup -1. Delete all three legacy harness directories -2. Remove all references from `src/server.tsx` and `src/servers/hono.ts` -3. Create new directory structure: `src/servers/api/pi/` - -### Phase 2: Core Infrastructure -1. Create `src/servers/api/pi/types.ts` — message types -2. Create `src/servers/api/pi/storage.ts` — disk I/O utilities -3. Create `src/servers/api/pi/pi-bridge.ts` — Pi RPC communication -4. Create `src/servers/api/pi/session-manager.ts` — in-memory session tracking - -### Phase 3: WebSocket Handler -1. Create `src/servers/api/pi/websocket.ts` — WebSocket bridge -2. Implement session lifecycle management -3. Implement Pi process spawning/cleanup -4. Implement message streaming and history injection - -### Phase 4: REST Endpoints -1. Create `src/servers/api/pi/rest.ts` — all REST handlers -2. Implement session listing, retrieval, update (rename), deletion -3. Implement search functionality -4. Implement model listing - -### Phase 5: Integration -1. Wire WebSocket into `src/server.tsx` -2. Wire REST routes into `src/servers/hono.ts` -3. Update `src/servers/api/chat-types.ts` if needed -4. Test all endpoints - -### Phase 6: Cleanup & Polish -1. Remove old type definitions not needed -2. Clean up any remaining references -3. Add logging/debugging -4. Document any edge cases - -### Phase 6.1: Session Grouping -1. Add group types and metadata structures -2. Update storage layer to support group directories -3. Implement group management REST endpoints -4. Update existing session endpoints to handle groups -5. Add WebSocket support for group assignment - ---- - -## Phase 6.1: Session Grouping — Detailed Implementation - -**Status**: ✅ COMPLETE -**Date**: February 20, 2026 - -### Overview - -Adds the ability to organize sessions into groups with natural language names, enabling better organization in the frontend chat list UI. - -### Directory Structure - -``` -{cwd}/ -└── .pi-sessions/ - ├── {sessionId}/ # Ungrouped session (root level) - │ ├── meta.json - │ └── messages.json - │ - ├── {anotherSessionId}/ # Another ungrouped session - │ ├── meta.json - │ └── messages.json - │ - └── @refactor-pi-harness/ # GROUP (folder with @ prefix) - ├── .group-meta.json # Group metadata - ├── {sessionId}/ # Session inside group - │ ├── meta.json # contains groupSlug field - │ └── messages.json - └── {sessionId}/ # Another session in group - ├── meta.json - └── messages.json -``` - -### Design Decisions - -**Group Identification**: -- Groups use `@` prefix in filesystem (e.g., `@refactor-pi-harness/`) -- Distinguishes groups from sessions without checking file contents - -**Group Metadata** (`.group-meta.json`): -```json -{ - "name": "Refactor Pi Harness", - "slug": "refactor-pi-harness", - "description": "Sessions related to refactoring the Pi harness", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "sessionCount": 5 -} -``` - -**Session Metadata Updates**: -```json -{ - "id": "session-uuid", - "title": "Fix WebSocket bug", - "groupSlug": "refactor-pi-harness", // null if ungrouped - "model": "gpt-4o", - "cwd": "/home/user/project", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "messageCount": 42, - "cost": { - "inputTokens": 5000, - "outputTokens": 3000, - "totalUSD": 0.15 - } -} -``` - -**Nesting Rules**: -- Groups cannot be nested (flat structure only) -- Sessions can only belong to one group at a time -- Sessions can be moved between groups or ungrouped - -**Group Deletion Behavior**: -- When a group is deleted, all sessions are moved to root level (ungrouped) -- Session data is preserved (non-destructive deletion) - -### Type Definitions - -**Added to `types.ts`**: - -```typescript -export type GroupMeta = { - name: string; // Natural language name - slug: string; // URL-friendly identifier (used in filesystem) - description?: string; // Optional description - createdAt: number; - updatedAt: number; - sessionCount: number; // Automatic count of sessions in group -}; - -export type SessionMeta = { - id: string; - title: string; - model: string; - cwd: string; - groupSlug?: string | null; // ⭐ NEW: Reference to parent group - createdAt: number; - updatedAt: number; - messageCount: number; - cost: MessageCost; -}; - -export type ClientMessage = - | { - type: "chat"; - prompt: string; - sessionId?: string; - model?: string; - cwd?: string; - groupSlug?: string; // ⭐ NEW: Assign session to group on creation - attachmentIds?: string[]; - } - // ... other message types -``` - -### Storage Functions - -**Added to `storage.ts`**: - -```typescript -// Group management -export async function saveGroup(cwd: string, groupMeta: GroupMeta): Promise -export async function loadGroup(cwd: string, groupSlug: string): Promise -export async function groupExists(cwd: string, groupSlug: string): Promise -export async function listGroups(baseCwd: string): Promise -export async function updateGroupMeta(cwd: string, groupSlug: string, updates: Partial): Promise -export async function deleteGroup(cwd: string, groupSlug: string): Promise - -// Session movement -export async function moveSession( - cwd: string, - sessionId: string, - fromGroupSlug: string | null, - toGroupSlug: string | null -): Promise -``` - -**Updated Existing Functions**: -All session functions now accept optional `groupSlug` parameter: -- `saveSession(cwd, sessionId, meta, messages)` — Uses `meta.groupSlug` -- `loadSession(cwd, sessionId, groupSlug?)` — Can specify group location -- `sessionExists(cwd, sessionId, groupSlug?)` -- `updateSessionMeta(cwd, sessionId, updates, groupSlug?)` -- `deleteSession(cwd, sessionId, groupSlug?)` - -**Enhanced Search**: -- `searchSessions()` now searches group names and descriptions -- Sessions in matching groups receive higher relevance scores -- Group name matches: +2.0 relevance -- Group description matches: +1.5 relevance - -### REST API Endpoints - -**New Group Endpoints**: - -#### Create Group -``` -POST /api/pi/groups -Authorization: Bearer {token} - -Body: -{ - "name": "Refactor Pi Harness", - "slug": "refactor-pi-harness", - "description": "Sessions related to refactoring", - "sessionIds": ["uuid1", "uuid2"] // optional, can be empty -} - -Response: -{ - "success": true, - "group": { - "name": "Refactor Pi Harness", - "slug": "refactor-pi-harness", - "description": "Sessions related to refactoring", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "sessionCount": 2 - } -} -``` - -#### List Groups -``` -GET /api/pi/groups -Authorization: Bearer {token} - -Response: -{ - "groups": [ - { - "name": "Refactor Pi Harness", - "slug": "refactor-pi-harness", - "description": "Sessions related to refactoring", - "createdAt": 1708396000000, - "updatedAt": 1708396000000, - "sessionCount": 5 - } - ] -} -``` - -#### Update Group -``` -PATCH /api/pi/groups/:groupSlug -Authorization: Bearer {token} - -Body: -{ - "name": "New Group Name", // optional - "description": "New description" // optional -} - -Response: -{ - "success": true, - "group": { /* updated group metadata */ } -} -``` - -#### Delete Group -``` -DELETE /api/pi/groups/:groupSlug -Authorization: Bearer {token} - -Response: -{ - "success": true -} - -Note: All sessions in the group are moved to root level (ungrouped) -``` - -#### Move Session -``` -POST /api/pi/sessions/:sessionId/move -Authorization: Bearer {token} - -Body: -{ - "groupSlug": "target-group" // or null to ungroup -} - -Response: -{ - "success": true, - "session": { /* updated session metadata */ } -} - -Note: Automatically updates session counts in both source and target groups -``` - -**Updated Session Endpoints**: - -All existing session endpoints now handle groups automatically: -- `GET /api/pi/sessions/:sessionId` — Searches in root and all groups -- `PATCH /api/pi/sessions/:sessionId` — Updates session in correct location -- `DELETE /api/pi/sessions/:sessionId` — Deletes and updates group count -- `GET /api/pi/sessions/search?q=query` — Includes group names/descriptions - -### WebSocket Integration - -**Creating Sessions with Groups**: - -```javascript -// Client sends -{ - "type": "chat", - "prompt": "Hello", - "model": "gpt-4o", - "groupSlug": "refactor-pi-harness" // ⭐ NEW: Optional group assignment -} - -// Server creates session in group -// Session metadata will have groupSlug set -``` - -### Session Manager Updates - -**`session-manager.ts`**: - -```typescript -getOrCreate( - sessionId: string, - email: string, - cwd: string, - model: string, - groupSlug?: string | null // ⭐ NEW parameter -): UserSession -``` - -Now initializes session metadata with `groupSlug` field. - -### Key Features - -✅ **Natural Language Names**: Groups have both display name and slug -✅ **Non-Destructive Deletion**: Deleting groups moves sessions to root -✅ **Automatic Counting**: Group session counts updated automatically -✅ **Bulk Creation**: Create group with initial sessionIds array -✅ **Enhanced Search**: Search includes group names and descriptions -✅ **Flexible Movement**: Move sessions between groups or ungroup them -✅ **WebSocket Support**: Assign sessions to groups during creation -✅ **Backward Compatible**: Existing ungrouped sessions continue to work - -### Frontend Integration Notes - -**For Phase 7 (Frontend Migration)**: - -1. **Chat List UI**: Display sessions grouped by `groupSlug` -2. **Group Management**: Add UI for creating/editing/deleting groups -3. **Drag & Drop**: Implement moving sessions between groups -4. **Search Enhancement**: Show group names in search results -5. **Session Creation**: Add group selector when starting new chats - -**Example Frontend Structure**: -``` -Chat History -├─ Ungrouped Sessions -│ ├─ Session 1 -│ └─ Session 2 -│ -├─ 📁 Refactor Pi Harness (5 sessions) -│ ├─ Fix WebSocket bug -│ ├─ Add session grouping -│ └─ Update documentation -│ -└─ 📁 Project X (3 sessions) - ├─ Initial setup - └─ API implementation -``` - -### Testing Checklist - -- [x] Create group with empty sessionIds array -- [x] Create group with initial sessions -- [x] List all groups sorted by updatedAt -- [x] Update group name and description -- [x] Delete group (sessions move to root) -- [x] Move session from root to group -- [x] Move session from group to group -- [x] Move session from group to root (ungroup) -- [x] Session counts update correctly on move/delete -- [x] Search includes group names and descriptions -- [x] WebSocket session creation with groupSlug -- [x] All existing endpoints work with grouped sessions -- [x] Group slug validation (prevents duplicate groups) -- [x] Session not found error handling - ---- - -## Phase 7: Frontend Migration — Detailed Implementation - -**Status**: 🚧 IN PROGRESS -**Date**: February 20, 2026 - -### Overview - -Migrate the frontend from legacy provider-specific hooks (`useClaude`, `useOpenCodeModels`, etc.) to unified Pi harness hooks. Update all API calls to use the new `/api/pi/*` endpoints. - -### Current Frontend State - -**WebSocket Hook**: `useClaude.ts` -- URL: `/api/harness/claudecode/ws` -- Handles: streaming, tools, sessions - -**Model Hooks**: Three separate hooks -- `useClaudeModels()` → `/claude/models` -- `useOpenCodeModels()` → `/opencode/models` -- `usePiMonoModels()` → `/pi-mono/models` - -**Session Management**: `useChatSessions.ts` -- Provider-specific: `'/sessions/${provider}/${sessionId}/...'` -- No group support - -### Phase 7.1: Type Alignment - -**Status**: ✅ COMPLETE -**Date**: February 20, 2026 - -Update frontend types to match new wire protocol. - -**What Was Done**: -- Created new types in `src/workspaces/apps/Chat/types.ts`: - - `MessageCost`, `SessionEntry`, `GroupEntry`, `ChatMessage`, `ServerMessage`, `Message` -- Created legacy type aliases for backward compatibility: - - `LegacyChatMessage`, `LegacySessionEntry`, `LegacyServerMessage` -- Updated all legacy hooks to use legacy types: - - `useClaude.ts` → `LegacyChatMessage`, `LegacyServerMessage` - - `useOpenCode.ts` → `LegacyChatMessage`, `LegacyServerMessage` - - `usePiMono.ts` → `LegacyChatMessage`, `LegacyServerMessage` -- Updated session management: - - `useChatSessions.ts` → `LegacySessionEntry`, `LegacyChatMessage` -- Updated UI components: - - `InputArea.tsx`, `Settings.tsx`, `TaskLogs/index.tsx` - - `MessageBubble.tsx`, `MessageList.tsx`, `QuestionActivity.tsx`, `ToolActivity.tsx` - -**Message Type Mapping**: - -| Old Type (Frontend) | New Type (Backend) | Action | -|---------------------|-------------------|--------| -| `session:init` | `session:init` | ✅ Keep | -| `assistant:partial` | `assistant:delta` | Rename | -| `assistant:text` | `assistant:text` | ✅ Keep | -| `tool:use` | `tool:start` | Rename | -| `tool:result` | `tool:result` | Update (add `toolCallId`) | -| `result` | `result` | Update (new cost structure) | -| N/A | `sync:messages` | Add (for resume) | - -**Files to Update**: -- `src/apps/officer-web/state/types/chat.ts` (or wherever ChatMessage types live) -- Any shared type definitions - -**New Cost Structure**: -```typescript -// Old -{ costUsd: number; durationMs: number; numTurns: number; } - -// New -{ - cost: { - inputTokens: number; - outputTokens: number; - totalUSD: number; - }; -} -``` - -### Phase 7.2: Unified Pi Hook (`usePi.ts`) - -Replace `useClaude.ts` with unified `usePi.ts`: - -**Key Changes**: -```typescript -// Old WebSocket URL -const wsUrl = `${protocol}//${host}/api/harness/claudecode/ws?token=${token}`; - -// New WebSocket URL -const wsUrl = `${protocol}//${host}/api/pi/chat/ws?token=${token}`; -``` - -**Updated Message Handling**: -```typescript -case 'assistant:delta': // renamed from 'assistant:partial' - streamingRef.current += msg.text; - flushStreaming(); - break; - -case 'tool:start': // renamed from 'tool:use' - setMessages((prev) => [ - ...prev, - { - role: 'tool', - toolName: msg.toolName, - toolInput: msg.toolInput, - toolCallId: msg.toolCallId, // renamed from toolUseId - }, - ]); - break; - -case 'sync:messages': // NEW: for session resume - setSessionId(msg.sessionId); - setMessages(msg.messages); - setIsGenerating(msg.isGenerating); - if (msg.streamingText) { - streamingRef.current = msg.streamingText; - flushStreaming(); - } - break; -``` - -**Updated sendPrompt**: -```typescript -send({ - type: 'chat', - prompt: text, - sessionId: sessionIdRef.current, - model: selectedModel, - cwd: cwd, - groupSlug: groupSlug, // NEW: group assignment - attachmentIds: attachmentIds, -}); -``` - -### Phase 7.3: Unified Models Hook - -Replace three model hooks with one: - -```typescript -// src/apps/officer-web/state/useModels.ts - -export const usePiModels = () => { - const client = useClient(); - const { isAuthenticated } = useAuth(); - - const { data: models = [] } = useQuery({ - queryKey: ['PI_MODELS'], - enabled: isAuthenticated, - queryFn: () => client.get('/api/pi/models'), - staleTime: 5 * 60 * 1000, - }); - - return models; -}; - -export const useVisiblePiModels = () => { - const models = usePiModels(); - const { settings } = useSettings(); - const enabled = settings.ai?.enabledModels ?? []; - return useMemo(() => { - const filtered = models.filter((m) => enabled.includes(modelKey(m))); - return filtered.length > 0 ? filtered : models; - }, [models, enabled]); -}; -``` - -### Phase 7.4: Session Management Migration - -Update `useChatSessions.ts`: - -**Endpoint Mapping**: - -| Old Endpoint | New Endpoint | -|--------------|--------------| -| `GET /sessions` | `POST /api/pi/sessions` | -| `GET /sessions/:provider/:id/messages` | `GET /api/pi/sessions/:id` | -| `PUT /sessions/:provider/:id` | `PATCH /api/pi/sessions/:id` | -| `POST /sessions/:provider/:id/archive` | Remove (not needed) | -| `DELETE /sessions/:provider/:id` | `DELETE /api/pi/sessions/:id` | -| N/A | `GET /api/pi/sessions/search?q=` | - -**Updated Hook**: -```typescript -export const useChatSessions = () => { - const client = useClient(); - const queryClient = useQueryClient(); - - const { data: sessions = [] } = useQuery({ - queryKey: ['PI_SESSIONS'], - queryFn: () => client.post<{ sessions: SessionMeta[] }>('/api/pi/sessions').then(r => r.sessions), - }); - - const getSession = (sessionId: string) => - client.get<{ session: SessionWithMessages }>(`/api/pi/sessions/${sessionId}`); - - const renameSession = async (sessionId: string, title: string) => { - await client.patch(`/api/pi/sessions/${sessionId}`, { title }); - queryClient.invalidateQueries({ queryKey: ['PI_SESSIONS'] }); - }; - - const deleteSession = async (sessionId: string) => { - await client.delete(`/api/pi/sessions/${sessionId}`); - queryClient.setQueryData( - ['PI_SESSIONS'], - (prev) => prev?.filter((s) => s.id !== sessionId) ?? [] - ); - }; - - const searchSessions = (query: string) => - client.get<{ results: SessionMeta[] }>(`/api/pi/sessions/search?q=${encodeURIComponent(query)}`); - - return { sessions, getSession, renameSession, deleteSession, searchSessions }; -}; -``` - -### Phase 7.5: Group Support Hooks - -Add new hooks for group management: - -```typescript -// src/apps/officer-web/state/useChatGroups.ts - -export const useChatGroups = () => { - const client = useClient(); - const queryClient = useQueryClient(); - - const { data: groups = [] } = useQuery({ - queryKey: ['PI_GROUPS'], - queryFn: () => client.get<{ groups: GroupMeta[] }>('/api/pi/groups').then(r => r.groups), - }); - - const createGroup = async (name: string, slug: string, description?: string, sessionIds?: string[]) => { - const result = await client.post<{ group: GroupMeta }>('/api/pi/groups', { - name, slug, description, sessionIds - }); - queryClient.invalidateQueries({ queryKey: ['PI_GROUPS'] }); - queryClient.invalidateQueries({ queryKey: ['PI_SESSIONS'] }); - return result.group; - }; - - const updateGroup = async (slug: string, updates: { name?: string; description?: string }) => { - await client.patch(`/api/pi/groups/${slug}`, updates); - queryClient.invalidateQueries({ queryKey: ['PI_GROUPS'] }); - }; - - const deleteGroup = async (slug: string) => { - await client.delete(`/api/pi/groups/${slug}`); - queryClient.invalidateQueries({ queryKey: ['PI_GROUPS'] }); - queryClient.invalidateQueries({ queryKey: ['PI_SESSIONS'] }); - }; - - const moveSession = async (sessionId: string, groupSlug: string | null) => { - await client.post(`/api/pi/sessions/${sessionId}/move`, { groupSlug }); - queryClient.invalidateQueries({ queryKey: ['PI_GROUPS'] }); - queryClient.invalidateQueries({ queryKey: ['PI_SESSIONS'] }); - }; - - return { groups, createGroup, updateGroup, deleteGroup, moveSession }; -}; -``` - -### Testing Checklist (Phase 7) - -- [ ] Type definitions match backend wire protocol -- [ ] `usePi` hook connects to new WebSocket endpoint -- [ ] Streaming text works correctly with `assistant:delta` -- [ ] Tool execution shows with `tool:start` / `tool:result` -- [ ] Session resume works with `sync:messages` -- [ ] Cost display shows new structure (inputTokens, outputTokens, totalUSD) -- [ ] Model picker uses unified `usePiModels` -- [ ] Session list loads from new endpoint -- [ ] Session rename works -- [ ] Session delete works -- [ ] Session search works -- [ ] Group list loads -- [ ] Create group works -- [ ] Move session to group works -- [ ] Delete group (sessions ungroup) works - ---- - -## Phase 8: UI Enhancements - -**Status**: Planned - -### Phase 8.1: Grouped ChatList UI - -Update ChatList component to display sessions organized by groups: - -``` -Chat History -├─ Ungrouped Sessions -│ ├─ Session 1 -│ └─ Session 2 -│ -├─ 📁 Refactor Pi Harness (5 sessions) -│ ├─ Fix WebSocket bug -│ ├─ Add session grouping -│ └─ Update documentation -│ -└─ 📁 Project X (3 sessions) - ├─ Initial setup - └─ API implementation -``` - -**Implementation**: -- Group sessions by `groupSlug` field -- Collapsible group sections -- Session count badges -- Sort groups by `updatedAt` -- Ungrouped sessions at top or bottom (configurable) - -### Phase 8.2: Group Management UI - -Add UI for managing groups: - -- **Create Group Dialog**: Name, slug (auto-generated), description -- **Rename Group**: Inline edit or dialog -- **Delete Group**: Confirmation dialog, explain sessions will ungroup -- **Drag & Drop**: Move sessions between groups (optional, can defer) - -### Phase 8.3: Search Enhancements - -Improve search functionality: - -- Show group names in search results -- Filter by group dropdown -- Highlight matching text in results -- Show relevance scores (optional) - -### Testing Checklist (Phase 8) - -- [ ] ChatList displays grouped structure -- [ ] Groups are collapsible -- [ ] Session counts are accurate -- [ ] Create group dialog works -- [ ] Rename group works -- [ ] Delete group shows confirmation -- [ ] Sessions ungroup correctly when group deleted -- [ ] Search shows group context -- [ ] Filter by group works - ---- - -## Phase 9: Final Cleanup - -**Status**: Planned - -### Phase 9.1: Frontend Legacy Cleanup - -Remove all legacy code: - -**Hooks to Delete**: -- `useClaudeModels()` from `useModels.ts` -- `useOpenCodeModels()` from `useModels.ts` -- `usePiMonoModels()` from `useModels.ts` -- `useVisibleClaudeModels()` from `useModels.ts` -- `useVisibleOpenCodeModels()` from `useModels.ts` -- `useVisiblePiMonoModels()` from `useModels.ts` - -**Files to Delete**: -- `useClaude.ts` (replaced by `usePi.ts`) -- `OpenCodeModelPicker.tsx` (if provider-specific) - -**Code to Update**: -- Remove `provider` parameter from all session functions -- Remove provider routing logic from ChatPanel -- Update all imports to use new hooks - -### Phase 9.2: Backend Final Cleanup - -Verify backend is clean: - -- Confirm old harness directories deleted (`src/servers/api/claude/`, etc.) -- Remove any remaining legacy routes from `hono.ts` -- Remove legacy WebSocket handlers from `server.tsx` -- Update any remaining type imports -- Clean up `chat-types.ts` if needed - -### Testing Checklist (Phase 9) - -- [ ] No TypeScript errors after cleanup -- [ ] Build succeeds -- [ ] No console errors in browser -- [ ] All chat features work end-to-end -- [ ] No dead code remaining -- [ ] Documentation up to date - ---- - -## Implementation Details - -### Session Manager (`session-manager.ts`) - -Tracks active Pi processes in memory: - -```typescript -type UserSession = { - sessionId: string; - email: string; - cwd: string; - model: string; - piProcess: Subprocess | null; - ws: ServerWebSocket | null; - lastActivity: number; - idleTimer: Timer | null; - streamBuffer: string; - isGenerating: boolean; - systemContextSent: boolean; -}; - -class SessionManager { - private sessions = new Map(); // sessionId → session - private userSessions = new Map(); // email → [sessionIds] - - getOrCreate(sessionId: string, email: string, cwd: string, model: string): UserSession; - getSession(sessionId: string): UserSession | null; - getUserSessions(email: string): UserSession[]; - deleteSession(sessionId: string): void; - attachWs(sessionId: string, ws: ServerWebSocket): void; - detachWs(sessionId: string): void; - setIdleTimeout(sessionId: string, timeoutMs: number): void; -} -``` - -### Storage (`storage.ts`) - -```typescript -class SessionStorage { - async saveSession( - cwd: string, - sessionId: string, - meta: SessionMeta, - messages: Message[] - ): Promise; - - async loadSession( - cwd: string, - sessionId: string - ): Promise<{ meta: SessionMeta; messages: Message[] }>; - - async listUserSessions( - email: string, - baseCwd?: string - ): Promise; - - async deleteSession( - cwd: string, - sessionId: string - ): Promise; - - async searchSessions( - email: string, - query: string, - baseCwd?: string - ): Promise; -} -``` - -### Pi Bridge (`pi-bridge.ts`) - -```typescript -class PiBridge { - async spawn( - cwd: string, - model: string, - env?: Record - ): Promise; - - sendPrompt( - process: Subprocess, - prompt: string, - requestId: string - ): void; - - abort( - process: Subprocess, - requestId: string - ): void; - - // Returns async generator of Pi events - readEvents(process: Subprocess): AsyncGenerator; -} -``` - -### WebSocket Lifecycle - -1. **Open**: Empty handler (connection established) -2. **Message**: - - If `type: 'chat'`: - - Load/create session - - Spawn Pi if needed - - Send prompt to Pi - - Start streaming responses - - If `type: 'resume'`: - - Load session from disk - - Spawn fresh Pi - - Send full history as system context - - Send `sync:messages` - - If `type: 'stop'`: - - Send abort to Pi -3. **Close**: - - Detach WebSocket from session - - Start idle timer (1 hour) - - On timeout: kill Pi, save session to disk, delete from memory - -### Key Features - -**History Injection**: -```typescript -function buildSystemPrompt(messages: Message[], homeDir: string, skills: string): string { - const history = messages - .map(msg => `${msg.role}: ${msg.text}`) - .join("\n"); - - return ` - -User home directory: ${homeDir} -${skills} - -Below is the conversation history from this session: - -${history} - - -`; -} -``` - -**Streaming Text**: -- Accumulate `assistant:delta` in `streamBuffer` -- Flush to state on `assistant:text` or message boundary -- Prevents render thrashing - -**Error Handling**: -- Pi process death → send error to client, save partial state -- WS disconnect → idle timeout, then cleanup -- Invalid JSON → send error, continue - ---- - -## Testing Checklist - -- [ ] Delete cleanup complete, no compilation errors -- [ ] New chat creates session on disk -- [ ] Resume old chat loads from disk and injects history -- [ ] Model switching works (different models per session) -- [ ] Multiple concurrent sessions don't interfere -- [ ] Idle timeout kills Pi process -- [ ] WS disconnect followed by reconnect works -- [ ] REST endpoints return correct data -- [ ] Search returns relevant results -- [ ] Streaming text appears correctly -- [ ] Tool execution tracked properly -- [ ] Session metadata updated on cost/message count - ---- - -## Notes - -- All paths use `{userCwd}/.pi-sessions/` for session storage -- Default cwd is `getHomeDir(email)` if not provided -- Pi processes spawned with `--mode rpc --no-extensions --no-skills` -- Message IDs should be UUIDs for uniqueness -- Timestamps in milliseconds (Date.now()) -- Cost tracking passed from Pi via `result` events - ---- - -**Ready to implement?** Confirm and start Phase 1. diff --git a/src/apps/officer-web/Screens/Dashboard/Chat/index.tsx b/src/apps/officer-web/Screens/Dashboard/Chat/index.tsx deleted file mode 100644 index 10b0bc8b..00000000 --- a/src/apps/officer-web/Screens/Dashboard/Chat/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export { ChatPanel } from './ChatPanel'; -export { EmbeddableChat, usePi, ChatList } from 'apps/Chat'; -export type { Attachment } from 'apps/Chat'; diff --git a/src/apps/officer-web/Screens/Dashboard/Chat/ChatPanel.tsx b/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx similarity index 90% rename from src/apps/officer-web/Screens/Dashboard/Chat/ChatPanel.tsx rename to src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx index c4aabb16..141e5759 100644 --- a/src/apps/officer-web/Screens/Dashboard/Chat/ChatPanel.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx @@ -11,7 +11,7 @@ type ChatPanelProps = { chat: UsePiType; }; -export const ChatPanel = ({ chat }: ChatPanelProps) => { +export const ChatScreen = ({ chat }: ChatPanelProps) => { const { isConnected, isGenerating, sessionId, setSelectedModel, sendPrompt } = chat; const location = useLocation(); @@ -31,16 +31,16 @@ export const ChatPanel = ({ chat }: ChatPanelProps) => { attachmentIds?: string[]; images?: { filename: string; dataUrl: string }[]; } | null; - + const initialMessage = locationState?.initialMessage ? { - text: locationState.initialMessage, - attachmentIds: locationState.attachmentIds, - images: locationState.images, - cwd: locationState.cwd, - } + text: locationState.initialMessage, + attachmentIds: locationState.attachmentIds, + images: locationState.images, + cwd: locationState.cwd, + } : undefined; - + const defaultInput = locationState?.prefillInput ?? ''; const initialModel = locationState?.model ?? null; diff --git a/src/apps/officer-web/Screens/Dashboard/ChatScreen/index.tsx b/src/apps/officer-web/Screens/Dashboard/ChatScreen/index.tsx new file mode 100644 index 00000000..18188300 --- /dev/null +++ b/src/apps/officer-web/Screens/Dashboard/ChatScreen/index.tsx @@ -0,0 +1 @@ +export * from './ChatScreen'; diff --git a/src/apps/officer-web/Screens/Dashboard/Home/index.tsx b/src/apps/officer-web/Screens/Dashboard/Home/index.tsx index 359a82a7..e29e2480 100644 --- a/src/apps/officer-web/Screens/Dashboard/Home/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Home/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import type { LayoutNode } from '@/components/Workspace'; -import { WorkspaceLayout } from '@/components/Workspace'; +import { WorkspaceView } from '@/components/Workspace'; import { appRegistry } from '../Workspaces/app-registry'; const initialLayout: LayoutNode = { @@ -30,7 +30,7 @@ export const HomeScreen = () => { return (
- +
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/Terminal/index.tsx b/src/apps/officer-web/Screens/Dashboard/Terminal/index.tsx index cea9c6e5..8b4d49e9 100644 --- a/src/apps/officer-web/Screens/Dashboard/Terminal/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Terminal/index.tsx @@ -5,7 +5,6 @@ import { WorkspaceView, createDefaultLayout } from '@/components/Workspace'; import type { LayoutNode } from '@/components/Workspace'; import { appRegistry } from '../Workspaces/app-registry'; -const defaultLayout: LayoutNode = { type: 'panel', id: 'terminal-root', appType: 'terminal-host' }; export const TerminalScreen = () => { const { user } = useAuth(); @@ -15,7 +14,17 @@ export const TerminalScreen = () => { return (
- +
); }; + +const defaultLayout: LayoutNode = { + type: 'panel', + id: 'terminal-screen', + appType: 'terminal-host' +}; diff --git a/src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx b/src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx index 07872a3e..ad04d4f8 100644 --- a/src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx @@ -10,7 +10,7 @@ import { useWorkspacesState } from '@/state/useWorkspacesState'; import { usePanelChannel } from 'hooks/usePanelChannel'; import { FileViewerProvider, FileViewerHeader, FileViewerBody } from 'apps/FileViewer'; import { usePi } from 'apps/Chat'; -import { ChatPanel } from '../Chat/ChatPanel'; +import { ChatPanel } from 'apps/Chat'; import { useVisiblePiModels } from '@/state/useModels'; import { ChatHistoryApp as ChatHistory } from '../ChatHistory'; import { Files } from '../Files'; diff --git a/src/apps/officer-web/Screens/Dashboard/index.tsx b/src/apps/officer-web/Screens/Dashboard/index.tsx index 71a73bea..f95c79c1 100644 --- a/src/apps/officer-web/Screens/Dashboard/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/index.tsx @@ -1,6 +1,6 @@ export * from './Layout'; export * from './Home'; -export * from './Chat'; +export * from './ChatScreen'; export * from './OnboardingAdmin'; export * from './PasskeyGate'; export * from './Plans'; diff --git a/src/apps/officer-web/state/types/user-settings.ts b/src/apps/officer-web/state/types/user-settings.ts deleted file mode 100644 index 1b8ac673..00000000 --- a/src/apps/officer-web/state/types/user-settings.ts +++ /dev/null @@ -1,55 +0,0 @@ -export type UserSettings = { - chat: { - defaultProvider: 'pi'; - defaultModel: string | null; - systemPrompt: string; - temperature: number; - defaultPwd: string; - }; - ai: { - enabledModels: string[]; - enabledProviders: string[]; - }; - tasks: { - defaultProvider: 'pi'; - defaultModel: string | null; - }; - appearance: { - colorMode: 'light' | 'dark'; - colorTheme: string; - }; - languages: { - spoken: string[]; - default: string; - translateTo: string; - }; -}; - -export type UserState = Record; - -export const DEFAULT_SETTINGS: UserSettings = { - chat: { - defaultProvider: 'pi', - defaultModel: null, - systemPrompt: '', - temperature: 1, - defaultPwd: '~', - }, - ai: { - enabledModels: [], - enabledProviders: [], - }, - tasks: { - defaultProvider: 'pi', - defaultModel: null, - }, - appearance: { - colorMode: 'light', - colorTheme: 'DuckPond', - }, - languages: { - spoken: ['en'], - default: 'en', - translateTo: 'en', - }, -}; diff --git a/src/apps/officer-web/state/useChatSessions.ts b/src/apps/officer-web/state/useChatSessions.ts index 1d0a9706..362aba93 100644 --- a/src/apps/officer-web/state/useChatSessions.ts +++ b/src/apps/officer-web/state/useChatSessions.ts @@ -3,23 +3,6 @@ import { useAuth } from 'hooks/useAuth'; import { useClient } from 'hooks/useClient'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -type SessionWithMessages = { - id: string; - title: string; - model: string; - cwd: string; - groupSlug?: string | null; - createdAt: number; - updatedAt: number; - messageCount: number; - cost: { - inputTokens: number; - outputTokens: number; - totalUSD: number; - }; - messages: Message[]; -}; - export function useChatSessions() { const client = useClient(); const queryClient = useQueryClient(); @@ -65,3 +48,22 @@ export function useChatSessions() { searchSessions, }; } + +export type UseChatSessionsType = ReturnType; + +type SessionWithMessages = { + id: string; + title: string; + model: string; + cwd: string; + groupSlug?: string | null; + createdAt: number; + updatedAt: number; + messageCount: number; + cost: { + inputTokens: number; + outputTokens: number; + totalUSD: number; + }; + messages: Message[]; +}; diff --git a/src/apps/officer-web/state/useSettings.ts b/src/apps/officer-web/state/useSettings.ts index 62fcfe0d..f0dca27d 100644 --- a/src/apps/officer-web/state/useSettings.ts +++ b/src/apps/officer-web/state/useSettings.ts @@ -2,8 +2,6 @@ import { useCallback } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useClient } from 'hooks/useClient'; import { useAuth } from 'hooks/useAuth'; -import type { UserSettings } from './types/user-settings'; -import { DEFAULT_SETTINGS } from './types/user-settings'; const QUERY_KEY = ['USER_SETTINGS']; @@ -43,3 +41,61 @@ export const useSettings = () => { return { settings, saveSettings }; }; + +export type UseSettingsType = ReturnType; + +export type UserSettings = { + chat: { + defaultProvider: 'pi'; + defaultModel: string | null; + systemPrompt: string; + temperature: number; + defaultPwd: string; + }; + ai: { + enabledModels: string[]; + enabledProviders: string[]; + }; + tasks: { + defaultProvider: 'pi'; + defaultModel: string | null; + }; + appearance: { + colorMode: 'light' | 'dark'; + colorTheme: string; + }; + languages: { + spoken: string[]; + default: string; + translateTo: string; + }; +}; + +export type UserState = Record; + +export const DEFAULT_SETTINGS: UserSettings = { + chat: { + defaultProvider: 'pi', + defaultModel: null, + systemPrompt: '', + temperature: 1, + defaultPwd: '~', + }, + ai: { + enabledModels: [], + enabledProviders: [], + }, + tasks: { + defaultProvider: 'pi', + defaultModel: null, + }, + appearance: { + colorMode: 'light', + colorTheme: 'DuckPond', + }, + languages: { + spoken: ['en'], + default: 'en', + translateTo: 'en', + }, +};