Restored from origin/main (b4663fb): - .github/ workflows and issue templates - .gitignore (proper exclusions) - .opencode/agent/web_developer.md - AGENTS.md, BUILD.md, PROGRESS.md - dev-docs/ (9 architecture/implementation docs) - docs/screenshots/ (4 UI screenshots) - images/ (CodeNomad icons) - package-lock.json (dependency lockfile) - tasks/ (25+ project task files) Also restored original source files that were modified: - packages/ui/src/App.tsx - packages/ui/src/lib/logger.ts - packages/ui/src/stores/instances.ts - packages/server/src/server/routes/workspaces.ts - packages/server/src/workspaces/manager.ts - packages/server/src/workspaces/runtime.ts - packages/server/package.json Kept new additions: - Install-*.bat/.sh (enhanced installers) - Launch-*.bat/.sh (new launchers) - README.md (SEO optimized with GLM 4.7)
313 lines
11 KiB
Markdown
313 lines
11 KiB
Markdown
# CodeNomad Architecture
|
|
|
|
## Overview
|
|
|
|
CodeNomad is a cross-platform desktop application built with Electron that provides a multi-instance, multi-session interface for interacting with OpenCode servers. Each instance manages its own OpenCode server process and can handle multiple concurrent sessions.
|
|
|
|
## High-Level Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ Electron Main Process │
|
|
│ - Window management │
|
|
│ - Process spawning (opencode serve) │
|
|
│ - IPC bridge to renderer │
|
|
│ - File system operations │
|
|
└────────────────┬────────────────────────────────────────┘
|
|
│ IPC
|
|
┌────────────────┴────────────────────────────────────────┐
|
|
│ Electron Renderer Process │
|
|
│ ┌──────────────────────────────────────────────────┐ │
|
|
│ │ SolidJS Application │ │
|
|
│ │ ┌────────────────────────────────────────────┐ │ │
|
|
│ │ │ Instance Manager │ │ │
|
|
│ │ │ - Spawns/kills OpenCode servers │ │ │
|
|
│ │ │ - Manages SDK clients per instance │ │ │
|
|
│ │ │ - Handles port allocation │ │ │
|
|
│ │ └────────────────────────────────────────────┘ │ │
|
|
│ │ ┌────────────────────────────────────────────┐ │ │
|
|
│ │ │ State Management (SolidJS Stores) │ │ │
|
|
│ │ │ - instances[] │ │ │
|
|
│ │ │ - sessions[] per instance │ │ │
|
|
│ │ │ - normalized message store per session │ │ │
|
|
│ │ └────────────────────────────────────────────┘ │ │
|
|
│ │ ┌────────────────────────────────────────────┐ │ │
|
|
│ │ │ UI Components │ │ │
|
|
│ │ │ - InstanceTabs │ │ │
|
|
│ │ │ - SessionTabs │ │ │
|
|
│ │ │ - MessageSection │ │ │
|
|
│ │ │ - PromptInput │ │ │
|
|
│ │ └────────────────────────────────────────────┘ │ │
|
|
│ └──────────────────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
│ HTTP/SSE
|
|
┌────────────────┴────────────────────────────────────────┐
|
|
│ Multiple OpenCode Server Processes │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Instance 1 │ │ Instance 2 │ │ Instance 3 │ │
|
|
│ │ Port: 4096 │ │ Port: 4097 │ │ Port: 4098 │ │
|
|
│ │ ~/project-a │ │ ~/project-a │ │ ~/api │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Component Layers
|
|
|
|
### 1. Main Process Layer (Electron)
|
|
|
|
**Responsibilities:**
|
|
|
|
- Create and manage application window
|
|
- Spawn OpenCode server processes as child processes
|
|
- Parse server stdout to extract port information
|
|
- Handle process lifecycle (start, stop, restart)
|
|
- Provide IPC handlers for renderer requests
|
|
- Manage native OS integrations (file dialogs, menus)
|
|
|
|
**Key Modules:**
|
|
|
|
- `main.ts` - Application entry point
|
|
- `process-manager.ts` - OpenCode server process spawning
|
|
- `ipc-handlers.ts` - IPC communication handlers
|
|
- `menu.ts` - Native application menu
|
|
|
|
### 2. Renderer Process Layer (SolidJS)
|
|
|
|
**Responsibilities:**
|
|
|
|
- Render UI components
|
|
- Manage application state
|
|
- Handle user interactions
|
|
- Communicate with OpenCode servers via HTTP/SSE
|
|
- Real-time message streaming
|
|
|
|
**Key Modules:**
|
|
|
|
- `App.tsx` - Root component
|
|
- `stores/` - State management
|
|
- `components/` - UI components
|
|
- `contexts/` - SolidJS context providers
|
|
- `lib/` - Utilities and helpers
|
|
|
|
### 3. Communication Layer
|
|
|
|
**HTTP API Communication:**
|
|
|
|
- SDK client per instance
|
|
- RESTful API calls for session/config/file operations
|
|
- Error handling and retries
|
|
|
|
**SSE (Server-Sent Events):**
|
|
|
|
- One EventSource per instance
|
|
- Real-time message updates
|
|
- Event type routing
|
|
- Reconnection logic
|
|
|
|
**CLI Proxy Paths:**
|
|
|
|
- The CLI server terminates all HTTP/SSE traffic and forwards it to the correct OpenCode instance.
|
|
- Each `WorkspaceDescriptor` exposes `proxyPath` (e.g., `/workspaces/<id>/instance`), which acts as the base URL for both REST and SSE calls.
|
|
- The renderer never touches the random per-instance port directly; it only talks to `window.location.origin + proxyPath` so a single CLI port can front every session.
|
|
|
|
## Data Flow
|
|
|
|
### Instance Creation Flow
|
|
|
|
1. User selects folder via Electron file dialog
|
|
2. Main process receives folder path via IPC
|
|
3. Main process spawns `opencode serve --port 0`
|
|
4. Main process parses stdout for port number
|
|
5. Main process sends port + PID back to renderer
|
|
6. Renderer creates SDK client for that port
|
|
7. Renderer fetches initial session list
|
|
8. Renderer displays session picker
|
|
|
|
### Message Streaming Flow
|
|
|
|
1. User submits prompt in active session
|
|
2. Renderer POSTs to `/session/:id/message`
|
|
3. SSE connection receives `MessageUpdated` events
|
|
4. Events are routed to correct instance → session
|
|
5. Message state updates trigger UI re-render
|
|
6. Messages display with auto-scroll
|
|
|
|
### Child Session Creation Flow
|
|
|
|
1. OpenCode server creates child session
|
|
2. SSE emits `SessionUpdated` event with `parentId`
|
|
3. Renderer adds session to instance's session list
|
|
4. New session tab appears automatically
|
|
5. Optional: Auto-switch to new tab
|
|
|
|
## State Management
|
|
|
|
### Instance State
|
|
|
|
```
|
|
instances: Map<instanceId, {
|
|
id: string
|
|
folder: string
|
|
port: number
|
|
pid: number
|
|
proxyPath: string // `/workspaces/:id/instance`
|
|
status: 'starting' | 'ready' | 'error' | 'stopped'
|
|
client: OpenCodeClient
|
|
eventSource: EventSource
|
|
sessions: Map<sessionId, Session>
|
|
activeSessionId: string | null
|
|
logs: string[]
|
|
}>
|
|
```
|
|
|
|
### Session State
|
|
|
|
```
|
|
Session: {
|
|
id: string
|
|
title: string
|
|
parentId: string | null
|
|
messages: Message[]
|
|
agent: string
|
|
model: { providerId: string, modelId: string }
|
|
status: 'idle' | 'streaming' | 'error'
|
|
}
|
|
```
|
|
|
|
### Message State
|
|
|
|
```
|
|
Message: {
|
|
id: string
|
|
sessionId: string
|
|
type: 'user' | 'assistant'
|
|
parts: Part[]
|
|
timestamp: number
|
|
status: 'sending' | 'sent' | 'streaming' | 'complete' | 'error'
|
|
}
|
|
```
|
|
|
|
## Tab Hierarchy
|
|
|
|
### Level 1: Instance Tabs
|
|
|
|
Each tab represents one OpenCode server instance:
|
|
|
|
- Label: Folder name (with counter if duplicate)
|
|
- Icon: Folder icon
|
|
- Close button: Stops server and closes tab
|
|
- "+" button: Opens folder picker for new instance
|
|
|
|
### Level 2: Session Tabs
|
|
|
|
Each instance has multiple session tabs:
|
|
|
|
- Main session tab (always present)
|
|
- Child session tabs (auto-created)
|
|
- Logs tab (shows server output)
|
|
- "+" button: Creates new session
|
|
|
|
### Tab Behavior
|
|
|
|
**Instance Tab Switching:**
|
|
|
|
- Preserves session tabs
|
|
- Switches active SDK client
|
|
- Updates SSE event routing
|
|
|
|
**Session Tab Switching:**
|
|
|
|
- Loads messages for that session
|
|
- Updates agent/model controls
|
|
- Preserves scroll position
|
|
|
|
## Technology Stack
|
|
|
|
### Core
|
|
|
|
- **Electron** - Desktop wrapper
|
|
- **SolidJS** - Reactive UI framework
|
|
- **TypeScript** - Type safety
|
|
- **Vite** - Build tool
|
|
|
|
### UI
|
|
|
|
- **TailwindCSS** - Styling
|
|
- **Kobalte** - Accessible UI primitives
|
|
- **Shiki** - Code syntax highlighting
|
|
- **Marked** - Markdown parsing
|
|
|
|
### Communication
|
|
|
|
- **OpenCode SDK** - API client
|
|
- **EventSource** - SSE streaming
|
|
- **Node Child Process** - Process spawning
|
|
|
|
## Error Handling
|
|
|
|
### Process Errors
|
|
|
|
- Server fails to start → Show error in instance tab
|
|
- Server crashes → Attempt auto-restart once
|
|
- Port already in use → Find next available port
|
|
|
|
### Network Errors
|
|
|
|
- API call fails → Show inline error, allow retry
|
|
- SSE disconnects → Auto-reconnect with backoff
|
|
- Timeout → Show timeout error, allow manual retry
|
|
|
|
### User Errors
|
|
|
|
- Invalid folder selection → Show error dialog
|
|
- Permission denied → Show actionable error message
|
|
- Out of memory → Graceful degradation message
|
|
|
|
## Performance Considerations
|
|
|
|
**Note: Performance optimization is NOT a focus for MVP. These are future considerations.**
|
|
|
|
### Message Rendering (Post-MVP)
|
|
|
|
- Start with simple list rendering - no virtual scrolling
|
|
- No message limits initially
|
|
- Only optimize if users report issues
|
|
- Virtual scrolling can be added in Phase 8 if needed
|
|
|
|
### State Updates
|
|
|
|
- SolidJS fine-grained reactivity handles most cases
|
|
- No special optimizations needed for MVP
|
|
- Batching/debouncing can be added later if needed
|
|
|
|
### Memory Management (Post-MVP)
|
|
|
|
- No memory management in MVP
|
|
- Let browser/OS handle it
|
|
- Add limits only if problems arise in testing
|
|
|
|
## Security Considerations
|
|
|
|
- No remote code execution
|
|
- Server spawned with user permissions
|
|
- No eval() or dangerous innerHTML
|
|
- Sanitize markdown rendering
|
|
- Validate all IPC messages
|
|
- HTTPS only for external requests
|
|
|
|
## Extensibility Points
|
|
|
|
### Plugin System (Future)
|
|
|
|
- Custom slash commands
|
|
- Custom message renderers
|
|
- Theme extensions
|
|
- Keybinding customization
|
|
|
|
### Configuration (Future)
|
|
|
|
- Per-instance settings
|
|
- Global preferences
|
|
- Workspace-specific configs
|
|
- Import/export settings
|