1180 lines
31 KiB
Markdown
1180 lines
31 KiB
Markdown
# Officer.dev Backend Architecture
|
|
|
|
## Executive Summary
|
|
|
|
Officer is a full-stack AI-assisted personal backend and frontend for life management. The backend is a **Bun-based monorepo** using:
|
|
- **Hono** framework for REST API (port 5000)
|
|
- **PostgreSQL** with Drizzle ORM for persistent data
|
|
- **WebSockets** for real-time terminal and AI chat functionality
|
|
- **Multi-workspace** shared libraries for code reuse
|
|
- **Node.js PTY sidecars** for terminal emulation
|
|
- **Pi coding agent integration** for AI-powered development assistance
|
|
|
|
This document covers the backend architecture, data flow, and technical patterns.
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Architecture Overview](#architecture-overview)
|
|
2. [Technology Stack](#technology-stack)
|
|
3. [Directory Structure](#directory-structure)
|
|
4. [Core Server](#core-server)
|
|
5. [API Architecture](#api-architecture)
|
|
6. [Database Design](#database-design)
|
|
7. [WebSocket Services](#websocket-services)
|
|
8. [Shared Workspaces](#shared-workspaces)
|
|
9. [Error Handling](#error-handling)
|
|
10. [Development & Deployment](#development--deployment)
|
|
11. [Key Features](#key-features)
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
### High-Level Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Web Browser (Frontend) │
|
|
└──────────────┬──────────────────────────────────────────────┘
|
|
│
|
|
├─ HTTP REST API (port 5000)
|
|
├─ WebSocket: Terminal (ws://api:5000/api/terminal/ws)
|
|
├─ WebSocket: Pi Chat (ws://api:5000/api/pi/chat/ws)
|
|
└─ Dev Server Proxy (ws://api:5000/api/dev-server-proxy/*)
|
|
│
|
|
┌──────────────▼──────────────────────────────────────────────┐
|
|
│ Bun Server (src/server.tsx + Hono Router) │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Middleware: │
|
|
│ - CORS handler │
|
|
│ - User authentication (JWT + token blacklist) │
|
|
│ - Request body parser │
|
|
│ │
|
|
│ Routes: │
|
|
│ - /api/auth → Authentication (login, signup) │
|
|
│ - /api/plans → Plans management │
|
|
│ - /api/skills → Skills (Pi integration) │
|
|
│ - /api/tasks → Tasks & processes │
|
|
│ - /api/upload → File uploads │
|
|
│ - /api/workspaces → Workspace management │
|
|
│ - /api/settings → User settings │
|
|
│ - /api/dev-server → Local dev server routing │
|
|
│ - WebSocket handlers → Real-time communication │
|
|
└──────────────┬──────────────────────────────────────────────┘
|
|
│
|
|
┌──────┴──────────────────────┐
|
|
│ │
|
|
▼ ▼
|
|
┌──────────────────┐ ┌──────────────────────┐
|
|
│ PostgreSQL DB │ │ Node.js PTY Sidecar │
|
|
│ (officer_db) │ │ (Terminal Emulation)│
|
|
│ │ │ │
|
|
│ Users, Plans, │ │ xterm-js │
|
|
│ Skills, Tasks, │ │ node-pty │
|
|
│ Sessions, etc │ │ Docker container │
|
|
└──────────────────┘ └──────────────────────┘
|
|
```
|
|
|
|
### Request/Response Flow
|
|
|
|
```
|
|
Client Request
|
|
↓
|
|
Server Router (Bun)
|
|
↓
|
|
Authentication Middleware
|
|
↓
|
|
Protected/Public Handler
|
|
↓
|
|
Database Query (Drizzle ORM) OR External Service
|
|
↓
|
|
Response (JSON/WebSocket)
|
|
↓
|
|
Client
|
|
```
|
|
|
|
---
|
|
|
|
## Technology Stack
|
|
|
|
### Core Runtime & Framework
|
|
- **Bun** (v1.30+) - Fast JavaScript runtime with built-in tooling
|
|
- **TypeScript** (v5.9+) - Strict mode enabled
|
|
- **Hono** (v4.11+) - Lightweight, type-safe web framework
|
|
- **Node.js** - For PTY sidecar processes
|
|
|
|
### Database & ORM
|
|
- **PostgreSQL** - Primary persistent data store
|
|
- **Drizzle ORM** (v0.45+) - Type-safe SQL query builder
|
|
- **Drizzle Kit** (v0.31+) - Schema management and migrations
|
|
|
|
### Real-Time Communication
|
|
- **WebSockets** (native Bun) - Bidirectional communication
|
|
- **Redis** (optional) - Session/cache storage
|
|
|
|
### Authentication & Security
|
|
- **JWT** - Token-based authentication
|
|
- **Argon2** - Password hashing (bcrypt alternative)
|
|
- **SimpleWebAuthn** - WebAuthn/FIDO2 passkey support
|
|
- **Token Blacklist** - Revocation tracking
|
|
|
|
### AI & Automation
|
|
- **@anthropic-ai/claude-agent-sdk** - Claude integration
|
|
- **Pi Coding Agent** - CLI-based AI development assistant
|
|
- **Nodemailer** - Email notifications
|
|
- **Googleapis** - Google integration for services
|
|
|
|
### Utilities
|
|
- **node-pty** - Cross-platform pseudoterminal support
|
|
- **Cron** (v4.3+) - Scheduled task execution
|
|
- **Date-fns** (v4.1+) - Date manipulation
|
|
- **Zod** (v4.2+) - Schema validation
|
|
|
|
---
|
|
|
|
## Directory Structure
|
|
|
|
### Complete Backend Layout
|
|
|
|
```
|
|
monorepo/
|
|
├── src/
|
|
│ ├── server.tsx # Main Bun server entry point
|
|
│ │
|
|
│ ├── servers/ # Backend core
|
|
│ │ ├── bootstrap.ts # Server initialization
|
|
│ │ ├── hono.ts # Hono app & router setup
|
|
│ │ ├── jwt.ts # JWT token handling
|
|
│ │ ├── custom-errors.ts # Custom error classes
|
|
│ │ ├── create-router.ts # Router factory with context
|
|
│ │ ├── data-path.ts # Path management
|
|
│ │ │
|
|
│ │ ├── _middlewares/ # Shared middleware
|
|
│ │ │ ├── user-middleware.ts # Auth verification
|
|
│ │ │ └── body-parser.ts # Request parsing
|
|
│ │ │
|
|
│ │ └── api/ # API routes
|
|
│ │ ├── auth/ # Authentication routes
|
|
│ │ │ ├── signin.ts # Login endpoint
|
|
│ │ │ ├── signup.ts # Registration endpoint
|
|
│ │ │ ├── signout.ts # Logout endpoint
|
|
│ │ │ ├── verify.ts # Email verification
|
|
│ │ │ ├── passkey-router.ts # WebAuthn endpoints
|
|
│ │ │ ├── reset-password.ts # Password reset
|
|
│ │ │ └── auth.ts # Core auth logic
|
|
│ │ │
|
|
│ │ ├── terminal/ # Terminal WebSocket
|
|
│ │ │ ├── websocket.ts # WS handler
|
|
│ │ │ └── pty-sidecar.mjs # PTY subprocess
|
|
│ │ │
|
|
│ │ ├── pi/ # Pi coding agent integration
|
|
│ │ │ ├── websocket.ts # Chat WS handler
|
|
│ │ │ ├── rest.ts # REST endpoints
|
|
│ │ │ ├── pi-bridge.ts # Pi SDK bridge
|
|
│ │ │ ├── storage.ts # Chat session storage
|
|
│ │ │ └── session-manager.ts # Session management
|
|
│ │ │
|
|
│ │ ├── dev-server/ # Local dev server routing
|
|
│ │ │ └── router.ts # Dev server proxy
|
|
│ │ │
|
|
│ │ ├── plans/ # Plans management
|
|
│ │ ├── skills/ # Skills routes
|
|
│ │ ├── tasks/ # Tasks & processes
|
|
│ │ ├── workspaces/ # Workspace management
|
|
│ │ ├── settings/ # User settings
|
|
│ │ ├── upload/ # File upload
|
|
│ │ ├── file-browser/ # File browser API
|
|
│ │ ├── scrape/ # Web scraping
|
|
│ │ ├── sessions/ # Chat sessions
|
|
│ │ ├── task-logs/ # Task execution logs
|
|
│ │ ├── server-settings/ # System configuration
|
|
│ │ └── users/ # User management
|
|
│ │
|
|
│ ├── databases/ # Data layer
|
|
│ │ └── officer_db/ # Main database package
|
|
│ │ ├── drizzle.config.ts # Drizzle configuration
|
|
│ │ ├── migrations/ # Database migrations
|
|
│ │ ├── src/
|
|
│ │ │ ├── schema.ts # Table definitions
|
|
│ │ │ ├── types.ts # Inferred types
|
|
│ │ │ └── index.ts # Exports
|
|
│ │ └── package.json # Workspace manifest
|
|
│ │
|
|
│ └── workspaces/ # Shared libraries
|
|
│ ├── components/ # React components
|
|
│ ├── hooks/ # React hooks
|
|
│ ├── helpers/ # Utility functions
|
|
│ ├── types/ # Shared types
|
|
│ ├── state/ # State management
|
|
│ ├── config/ # Configuration
|
|
│ ├── definitions/ # Constants
|
|
│ ├── emailer/ # Email service
|
|
│ ├── injector/ # Dependency injection
|
|
│ └── i18n/ # Internationalization
|
|
│
|
|
├── package.json # Root workspace config
|
|
├── bunfig.toml # Bun configuration
|
|
├── tsconfig.json # TypeScript config
|
|
└── .env # Environment variables
|
|
```
|
|
|
|
---
|
|
|
|
## Core Server
|
|
|
|
### Entry Point: `src/server.tsx`
|
|
|
|
The main server file initializes the Bun HTTP server and handles:
|
|
1. Route registration (static files, API, WebSockets)
|
|
2. WebSocket upgrade logic and authentication
|
|
3. Dev server proxy for HMR and local dev environments
|
|
4. PI coding agent installation/verification
|
|
|
|
#### Key Responsibilities
|
|
|
|
```typescript
|
|
// 1. Static file serving
|
|
'/static/*' → public/ directory files
|
|
|
|
// 2. HTML fallback for SPA routing
|
|
'/' and '/*' → officer-web app
|
|
|
|
// 3. API routing
|
|
'/api/*' → honoServer (REST endpoints)
|
|
|
|
// 4. WebSocket routing with auth
|
|
'/api/terminal/ws' → Terminal WebSocket
|
|
'/api/pi/chat/ws' → Pi Chat WebSocket
|
|
|
|
// 5. Dev server proxy
|
|
'/api/dev-server-proxy/*' → Proxied WS for local dev servers
|
|
```
|
|
|
|
#### WebSocket Authentication Pattern
|
|
|
|
```typescript
|
|
async function upgradeWs(req: Request, server: any, provider: 'terminal' | 'pi') {
|
|
// 1. Extract JWT token from query params
|
|
const token = new URL(req.url).searchParams.get('token');
|
|
|
|
// 2. Verify token and extract user
|
|
const user = await verify(token);
|
|
|
|
// 3. Check token blacklist (for revoked tokens)
|
|
const blacklisted = await officerdb.query.TokenBlacklist.findFirst({...});
|
|
|
|
// 4. Extract WS-specific parameters
|
|
const sessionId = url.searchParams.get('sessionId');
|
|
const cwd = url.searchParams.get('cwd');
|
|
const cols = url.searchParams.get('cols'); // terminal dimensions
|
|
|
|
// 5. Upgrade connection with authenticated data
|
|
server.upgrade(req, { data: { userId, email, role, provider, ... } });
|
|
}
|
|
```
|
|
|
|
### Hono Server: `src/servers/hono.ts`
|
|
|
|
Hono is a lightweight web framework perfect for edge computing and Bun.
|
|
|
|
#### Server Initialization
|
|
|
|
```typescript
|
|
const honoServer = new Hono<{ Variables: HonoVariables }>();
|
|
|
|
honoServer.use(cors({ origin: '*', allowMethods: [...] }));
|
|
|
|
// Public routes
|
|
honoServer.get('/api', (ctx) => ctx.json({ officerAPI: 'ok' }));
|
|
honoServer.route('/api/auth', authRouter);
|
|
honoServer.route('/api/server-settings', serverSettingsRouter);
|
|
|
|
// Protected routes
|
|
const protectedRouter = createRouter();
|
|
protectedRouter.use(bodyParser());
|
|
protectedRouter.use(userMiddleware);
|
|
|
|
protectedRouter.route('/plans', plansRouter);
|
|
protectedRouter.route('/tasks', tasksRouter);
|
|
// ... other routes
|
|
|
|
honoServer.route('/api', protectedRouter);
|
|
|
|
// Error handling
|
|
honoServer.onError((error, ctx) => {
|
|
if (error instanceof CustomError) {
|
|
return ctx.json(error.returnValue, error.statusCode);
|
|
}
|
|
return ctx.text('Internal Server Error', 500);
|
|
});
|
|
```
|
|
|
|
#### Hono Variables Context
|
|
|
|
```typescript
|
|
type HonoVariables = {
|
|
userId: number;
|
|
email: string;
|
|
role: 'admin' | 'user';
|
|
// Accessible in all routes via ctx.get('userId')
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## API Architecture
|
|
|
|
### Route Organization
|
|
|
|
The API is organized by feature domain:
|
|
|
|
```
|
|
/api/
|
|
├── auth/ # User authentication (public)
|
|
│ ├── POST /signin
|
|
│ ├── POST /signup
|
|
│ ├── POST /signout
|
|
│ ├── GET /verify
|
|
│ ├── POST /passkeys (WebAuthn)
|
|
│ └── ...
|
|
│
|
|
├── plans/ # Plans management (protected)
|
|
├── skills/ # Skills endpoints
|
|
├── tasks/ # Task operations
|
|
├── workspaces/ # Workspace operations
|
|
├── settings/ # User preferences
|
|
├── upload/ # File uploads
|
|
├── file-browser/ # File system access
|
|
├── terminal/ws # Terminal emulation (WS)
|
|
├── pi/ # AI assistant integration
|
|
│ ├── /ws # Chat (WebSocket)
|
|
│ └── /rest # REST endpoints
|
|
│
|
|
└── dev-server/ # Local dev server routing
|
|
```
|
|
|
|
### Router Creation Pattern: `src/servers/create-router.ts`
|
|
|
|
```typescript
|
|
// Factory function for creating contextualized routers
|
|
export function createRouter() {
|
|
return new Hono<{ Variables: HonoVariables }>();
|
|
}
|
|
|
|
// Usage in route handlers:
|
|
const router = createRouter();
|
|
router.get('/endpoint', (ctx) => {
|
|
const userId = ctx.get('userId');
|
|
// Route logic
|
|
});
|
|
```
|
|
|
|
### Error Handling: `src/servers/custom-errors.ts`
|
|
|
|
```typescript
|
|
class CustomError extends Error {
|
|
constructor(
|
|
public statusCode: number,
|
|
message: string,
|
|
public returnValue?: Record<string, any> | string,
|
|
) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
// Usage patterns
|
|
throw new CustomError(400, 'Bad request', { error: 'Invalid input' });
|
|
throw new CustomError(401, 'Unauthorized');
|
|
throw new CustomError(500, 'Internal error', { error: 'Database failed' });
|
|
```
|
|
|
|
---
|
|
|
|
## Database Design
|
|
|
|
### Primary Database: `officer_db`
|
|
|
|
Located in `src/databases/officer_db/`, this is the main persistent data store.
|
|
|
|
#### Drizzle Configuration
|
|
|
|
```typescript
|
|
// drizzle.config.ts
|
|
import { defineConfig } from 'drizzle-kit';
|
|
|
|
export default defineConfig({
|
|
schema: './src/schema.ts',
|
|
out: './migrations',
|
|
dialect: 'postgresql',
|
|
dbCredentials: {
|
|
url: process.env.DATABASE_URL!,
|
|
},
|
|
});
|
|
```
|
|
|
|
#### Schema Structure
|
|
|
|
The schema defines all tables and their relationships:
|
|
|
|
```typescript
|
|
// src/databases/officer_db/src/schema.ts
|
|
|
|
// Core tables
|
|
export const Users = pgTable('users', {
|
|
id: serial('id').primaryKey(),
|
|
email: text('email').unique().notNull(),
|
|
password: text('password'),
|
|
role: text('role').$type<'admin' | 'user'>().default('user'),
|
|
verified: boolean('verified').default(false),
|
|
createdAt: timestamp('created_at').defaultNow(),
|
|
updatedAt: timestamp('updated_at').defaultNow(),
|
|
});
|
|
|
|
export const Plans = pgTable('plans', {
|
|
id: serial('id').primaryKey(),
|
|
userId: integer('user_id').references(() => Users.id),
|
|
title: text('title').notNull(),
|
|
description: text('description'),
|
|
// ... other fields
|
|
});
|
|
|
|
export const Skills = pgTable('skills', {
|
|
id: serial('id').primaryKey(),
|
|
userId: integer('user_id').references(() => Users.id),
|
|
name: text('name').notNull(),
|
|
content: text('content'),
|
|
// ... other fields
|
|
});
|
|
|
|
// ... many more tables for tasks, sessions, workspaces, etc.
|
|
|
|
// Relations
|
|
export const usersRelations = relations(Users, ({ many }) => ({
|
|
plans: many(Plans),
|
|
skills: many(Skills),
|
|
}));
|
|
```
|
|
|
|
#### Type Generation
|
|
|
|
Types are automatically inferred from schema:
|
|
|
|
```typescript
|
|
// src/databases/officer_db/src/types.ts
|
|
|
|
// Simple table types
|
|
export type User = typeof Users.$inferSelect;
|
|
export type UserInsert = typeof Users.$inferInsert;
|
|
|
|
// Extended types for API responses
|
|
export type UserWithRelations = User & {
|
|
plans: Plan[];
|
|
skills: Skill[];
|
|
};
|
|
```
|
|
|
|
#### Database Operations in Routes
|
|
|
|
```typescript
|
|
import { officerdb, eq, Users } from 'officerdb';
|
|
|
|
// Simple query
|
|
const user = await officerdb.query.Users.findFirst({
|
|
where: eq(Users.id, userId),
|
|
});
|
|
|
|
// Query with relations
|
|
const user = await officerdb.query.Users.findFirst({
|
|
where: eq(Users.id, userId),
|
|
with: {
|
|
plans: true,
|
|
skills: true,
|
|
},
|
|
});
|
|
|
|
// Insert with returning
|
|
const [newUser] = await officerdb.insert(Users).values({
|
|
email: 'user@example.com',
|
|
password: hashedPassword,
|
|
}).returning();
|
|
|
|
// Update
|
|
await officerdb.update(Users).set({
|
|
verified: true,
|
|
}).where(eq(Users.id, userId));
|
|
```
|
|
|
|
#### Token Blacklist Table
|
|
|
|
```typescript
|
|
export const TokenBlacklist = pgTable('token_blacklist', {
|
|
id: serial('id').primaryKey(),
|
|
jti: text('jti').unique().notNull(), // JWT ID
|
|
expiresAt: timestamp('expires_at').notNull(),
|
|
createdAt: timestamp('created_at').defaultNow(),
|
|
});
|
|
|
|
// Used for token revocation (logout, password change, etc.)
|
|
```
|
|
|
|
### Database Migrations
|
|
|
|
Migrations are generated by Drizzle and stored in `src/databases/officer_db/migrations/`:
|
|
|
|
```bash
|
|
# Generate new migration
|
|
bun run db:gen
|
|
|
|
# Apply migrations
|
|
bun run db:push
|
|
|
|
# Open Drizzle Studio for visual management
|
|
bun run db:studio
|
|
```
|
|
|
|
---
|
|
|
|
## WebSocket Services
|
|
|
|
### 1. Terminal WebSocket: `src/servers/api/terminal/`
|
|
|
|
Provides an interactive terminal emulator in the browser using xterm.js.
|
|
|
|
#### Architecture
|
|
|
|
```
|
|
Browser (xterm.js)
|
|
↓ WebSocket
|
|
Officer Server (websocket.ts)
|
|
↓ IPC/Stdio
|
|
PTY Sidecar (pty-sidecar.mjs)
|
|
↓ Shell Process
|
|
System Shell (bash/zsh)
|
|
```
|
|
|
|
#### Handler: `websocket.ts`
|
|
|
|
```typescript
|
|
export const terminalWebsocket = {
|
|
// Connection established
|
|
open(ws: ServerWebSocket<WSData>) {
|
|
const { userId, cwd, cols, rows, sandboxed } = ws.data;
|
|
|
|
// 1. Spawn or reuse PTY sidecar
|
|
// 2. Set terminal dimensions
|
|
// 3. Send initial prompt
|
|
},
|
|
|
|
// Client sends data (typing, etc.)
|
|
message(ws: ServerWebSocket<WSData>, raw: string | Buffer) {
|
|
// 1. Parse message type (input, resize, etc.)
|
|
// 2. Forward to PTY process
|
|
},
|
|
|
|
// Connection closed
|
|
close(ws: ServerWebSocket<WSData>) {
|
|
// 1. Kill PTY process
|
|
// 2. Cleanup resources
|
|
},
|
|
};
|
|
```
|
|
|
|
#### PTY Sidecar: `pty-sidecar.mjs`
|
|
|
|
A Node.js subprocess that manages the actual pseudoterminal:
|
|
|
|
```javascript
|
|
// Uses node-pty for cross-platform terminal support
|
|
const pty = require('node-pty');
|
|
|
|
const term = pty.spawn('bash', [], {
|
|
name: 'xterm-color',
|
|
cols: 120,
|
|
rows: 40,
|
|
cwd: process.env.CWD,
|
|
});
|
|
|
|
// stdout → send to client
|
|
term.on('data', (data) => {
|
|
process.stdout.write(JSON.stringify({ type: 'output', data }));
|
|
});
|
|
|
|
// stdin from client → write to terminal
|
|
process.stdin.on('data', (chunk) => {
|
|
const msg = JSON.parse(chunk);
|
|
if (msg.type === 'input') term.write(msg.data);
|
|
if (msg.type === 'resize') term.resize(msg.cols, msg.rows);
|
|
});
|
|
```
|
|
|
|
#### Sandboxed vs. Real Terminals
|
|
|
|
- **Sandboxed**: Limited to specific directories, no system access
|
|
- **Real**: Full system access from user's working directory
|
|
|
|
### 2. Pi Coding Agent WebSocket: `src/servers/api/pi/`
|
|
|
|
Integrates the Pi coding agent for AI-assisted development.
|
|
|
|
#### Architecture
|
|
|
|
```
|
|
Browser (Chat UI)
|
|
↓ WebSocket
|
|
Officer Server (pi/websocket.ts)
|
|
↓ SDK
|
|
Pi Coding Agent (pi-bridge.ts)
|
|
↓ RPC/Tools
|
|
LLM API (Claude, GPT, etc.)
|
|
```
|
|
|
|
#### Components
|
|
|
|
**websocket.ts**: WebSocket message handler
|
|
- Routes incoming chat messages to Pi
|
|
- Streams responses back to client
|
|
- Manages session state
|
|
|
|
**pi-bridge.ts**: SDK integration layer
|
|
```typescript
|
|
export async function createPiSession(
|
|
cwd: string,
|
|
userEmail: string,
|
|
sessionId?: string,
|
|
) {
|
|
// 1. Initialize Pi SDK
|
|
const { session } = await createAgentSession({
|
|
cwd,
|
|
model: selectedModel,
|
|
tools: createCodingTools(cwd),
|
|
});
|
|
|
|
// 2. Subscribe to events
|
|
session.subscribe((event) => {
|
|
// Stream events back to client via WS
|
|
});
|
|
|
|
// 3. Return session for messaging
|
|
return session;
|
|
}
|
|
```
|
|
|
|
**rest.ts**: REST endpoints for Pi operations
|
|
- Create new sessions
|
|
- List sessions
|
|
- Get session details
|
|
- Export conversations
|
|
|
|
**storage.ts**: Chat session persistence
|
|
```typescript
|
|
// Store conversation history in database
|
|
interface PiSession {
|
|
id: string;
|
|
userId: number;
|
|
cwd: string;
|
|
model: string;
|
|
createdAt: timestamp;
|
|
messages: PiMessage[];
|
|
}
|
|
|
|
// Retrieve from database when resuming
|
|
```
|
|
|
|
**session-manager.ts**: Session lifecycle management
|
|
|
|
#### Message Flow
|
|
|
|
```
|
|
Client: "ls -la"
|
|
→ WebSocket to Officer
|
|
→ Pi process stdin
|
|
→ Pi executes bash tool
|
|
→ Tool output
|
|
→ Stream back to client
|
|
```
|
|
|
|
#### Integration with Tools
|
|
|
|
Pi can execute:
|
|
- `read` - Read files
|
|
- `bash` - Run shell commands
|
|
- `edit` - Modify files
|
|
- `write` - Create files
|
|
- Custom tools via extensions
|
|
|
|
---
|
|
|
|
## Shared Workspaces
|
|
|
|
Shared code is organized in `src/workspaces/` as Bun workspace packages:
|
|
|
|
### Key Workspaces
|
|
|
|
**types/** - TypeScript type definitions
|
|
- Centralized, exported via `'types'` alias
|
|
- Shared by frontend and backend
|
|
- Database types imported and re-exported
|
|
|
|
**helpers/** - Utility functions
|
|
- Formatters, validators, converters
|
|
- Pure functions with no side effects
|
|
|
|
**hooks/** - React hooks
|
|
- Frontend-only
|
|
- `useAuth`, `useQuery`, `useMutation`, etc.
|
|
|
|
**state/** - State management
|
|
- Global state stores
|
|
- Context providers
|
|
- Zustand/React Context usage
|
|
|
|
**components/** - React components
|
|
- UI components (buttons, inputs, etc.)
|
|
- Complex feature components
|
|
- Reusable across apps
|
|
|
|
**config/** - Configuration
|
|
- Constants
|
|
- Environment-specific settings
|
|
|
|
**definitions/** - Enum and constant definitions
|
|
- User roles
|
|
- Status values
|
|
- Feature flags
|
|
|
|
**emailer/** - Email service
|
|
```typescript
|
|
// Nodemailer-based email sending
|
|
export async function sendEmail(to: string, subject: string, html: string) {
|
|
const transporter = nodemailer.createTransport({...});
|
|
return transporter.sendMail({ to, subject, html });
|
|
}
|
|
```
|
|
|
|
**injector/** - Dependency injection
|
|
- Service locator pattern
|
|
- Configuration management
|
|
|
|
**i18n/** - Internationalization
|
|
- Multi-language support
|
|
- Locale management
|
|
|
|
**sounds/** - Audio assets
|
|
- Notification sounds
|
|
- UI feedback audio
|
|
|
|
**widgets/** - Complex UI widgets
|
|
- Feature-rich components
|
|
- Composed from basic components
|
|
|
|
### Workspace Configuration
|
|
|
|
Each workspace has a `package.json`:
|
|
|
|
```json
|
|
{
|
|
"name": "components",
|
|
"version": "0.1.0",
|
|
"type": "module",
|
|
"exports": {
|
|
".": "./index.ts"
|
|
},
|
|
"main": "./index.ts"
|
|
}
|
|
```
|
|
|
|
### Import Pattern in Code
|
|
|
|
```typescript
|
|
// In any app (frontend/backend)
|
|
import { User, Plan } from 'types';
|
|
import { formatDate } from 'helpers';
|
|
import { useAuth } from 'hooks';
|
|
import { useGlobalState } from 'state';
|
|
import { Button } from 'components';
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Error Classification
|
|
|
|
```typescript
|
|
// Custom error hierarchy
|
|
class CustomError extends Error {
|
|
constructor(
|
|
public statusCode: number,
|
|
message: string,
|
|
public returnValue?: any,
|
|
) {}
|
|
}
|
|
|
|
// Usage patterns
|
|
throw new CustomError(400, 'Bad request', { field: 'email', error: 'Invalid' });
|
|
throw new CustomError(401, 'Unauthorized');
|
|
throw new CustomError(403, 'Forbidden');
|
|
throw new CustomError(404, 'Not found', { resource: 'Plan' });
|
|
throw new CustomError(409, 'Conflict', { error: 'Email already exists' });
|
|
throw new CustomError(500, 'Internal server error');
|
|
```
|
|
|
|
### Middleware Error Handling
|
|
|
|
```typescript
|
|
honoServer.onError((error, ctx) => {
|
|
// CustomError → formatted response
|
|
if (error instanceof CustomError) {
|
|
return ctx.json(error.returnValue || error.message, error.statusCode);
|
|
}
|
|
|
|
// Unexpected error → 500
|
|
console.error('Unexpected error:', error.message);
|
|
return ctx.text('Internal Server Error', 500);
|
|
});
|
|
```
|
|
|
|
### Try/Catch Pattern
|
|
|
|
```typescript
|
|
// In route handlers
|
|
router.post('/endpoint', async (ctx) => {
|
|
try {
|
|
const body = await ctx.req.json();
|
|
|
|
// Validation
|
|
if (!body.email) {
|
|
throw new CustomError(400, 'Email required', { field: 'email' });
|
|
}
|
|
|
|
// Database operation
|
|
const user = await officerdb.insert(Users).values({...}).returning();
|
|
if (!user) throw new CustomError(500, 'Failed to create user');
|
|
|
|
return ctx.json({ success: true, user });
|
|
} catch (error) {
|
|
// Re-throw CustomErrors, let middleware handle
|
|
if (error instanceof CustomError) throw error;
|
|
|
|
// Unexpected error
|
|
console.error('Route error:', error);
|
|
throw new CustomError(500, 'Internal server error');
|
|
}
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication System
|
|
|
|
### JWT Flow
|
|
|
|
```
|
|
User Login
|
|
↓
|
|
Verify credentials (email + password)
|
|
↓
|
|
Generate JWT token
|
|
{
|
|
"sub": userId,
|
|
"email": email,
|
|
"role": "user",
|
|
"iat": timestamp,
|
|
"exp": timestamp,
|
|
"jti": unique-token-id
|
|
}
|
|
↓
|
|
Return token to client
|
|
↓
|
|
Client stores in localStorage
|
|
↓
|
|
Client sends in Authorization header
|
|
↓
|
|
Server verifies signature & expiration
|
|
↓
|
|
Grant access
|
|
```
|
|
|
|
### Token Verification: `src/servers/jwt.ts`
|
|
|
|
```typescript
|
|
export async function verify(token: string) {
|
|
try {
|
|
// 1. Verify signature and expiration
|
|
const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload;
|
|
|
|
// 2. Check token blacklist (for revoked tokens)
|
|
if (decoded.jti) {
|
|
const blacklisted = await officerdb.query.TokenBlacklist.findFirst({
|
|
where: eq(TokenBlacklist.jti, decoded.jti),
|
|
});
|
|
if (blacklisted) return null; // Token revoked
|
|
}
|
|
|
|
return { id: decoded.sub, email: decoded.email, role: decoded.role };
|
|
} catch {
|
|
return null; // Invalid token
|
|
}
|
|
}
|
|
```
|
|
|
|
### Logout with Token Blacklist
|
|
|
|
```typescript
|
|
router.post('/signout', async (ctx) => {
|
|
const token = ctx.req.header('Authorization')?.replace('Bearer ', '');
|
|
const decoded = jwt.decode(token) as JWTPayload;
|
|
|
|
// Add to blacklist
|
|
await officerdb.insert(TokenBlacklist).values({
|
|
jti: decoded.jti,
|
|
expiresAt: new Date(decoded.exp * 1000),
|
|
});
|
|
|
|
return ctx.json({ success: true });
|
|
});
|
|
```
|
|
|
|
### WebAuthn/Passkey Support
|
|
|
|
```typescript
|
|
// Registration
|
|
router.post('/passkeys/register/options', async (ctx) => {
|
|
const user = ctx.get('userId');
|
|
const options = await generateRegistrationOptions({
|
|
rpID: 'officer.dev',
|
|
rpName: 'Officer',
|
|
userID: Buffer.from(String(user.id)),
|
|
userName: user.email,
|
|
});
|
|
return ctx.json(options);
|
|
});
|
|
|
|
// Verification
|
|
router.post('/passkeys/register/verify', async (ctx) => {
|
|
const credential = await ctx.req.json();
|
|
const verification = await verifyRegistrationResponse({
|
|
credential,
|
|
expectedOrigin: 'https://officer.dev',
|
|
expectedRPID: 'officer.dev',
|
|
});
|
|
|
|
if (verification.verified) {
|
|
// Store passkey
|
|
await officerdb.insert(Passkeys).values({...});
|
|
}
|
|
return ctx.json({ verified: verification.verified });
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Development & Deployment
|
|
|
|
### Development Server
|
|
|
|
```bash
|
|
# Start with hot reload
|
|
bun dev
|
|
|
|
# Server runs on http://localhost:5000
|
|
# Frontend available at http://localhost:5000/
|
|
```
|
|
|
|
### Database Operations
|
|
|
|
```bash
|
|
# Generate migration from schema changes
|
|
bun run db:gen
|
|
|
|
# Apply pending migrations
|
|
bun run db:push
|
|
|
|
# Open Drizzle Studio (UI for database)
|
|
bun run db:studio
|
|
```
|
|
|
|
### Build & Production
|
|
|
|
```bash
|
|
# Full build
|
|
bun run build
|
|
|
|
# Production server
|
|
NODE_ENV=production bun src/server.tsx
|
|
|
|
# Runs on port specified by PORT env var (default 5000)
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
```bash
|
|
# .env
|
|
DATABASE_URL=postgresql://user:pass@localhost:5432/officer
|
|
JWT_SECRET=your-secret-key
|
|
NODE_ENV=development
|
|
PORT=5000
|
|
```
|
|
|
|
---
|
|
|
|
## Key Features
|
|
|
|
### 1. Real-Time Terminal
|
|
|
|
- Interactive shell in browser
|
|
- File operations
|
|
- Code execution
|
|
- Multi-session support
|
|
- Sandboxed mode option
|
|
|
|
### 2. AI-Powered Development with Pi
|
|
|
|
- Chat interface with Claude
|
|
- File reading/writing capabilities
|
|
- Shell command execution
|
|
- Session persistence
|
|
- Model selection
|
|
|
|
### 3. Task Management
|
|
|
|
- Create and track tasks
|
|
- Task dependencies
|
|
- Scheduled execution (cron)
|
|
- Task logs and history
|
|
- Process management
|
|
|
|
### 4. Workspace Management
|
|
|
|
- Multi-workspace support
|
|
- Project organization
|
|
- Settings per workspace
|
|
- Resource management
|
|
|
|
### 5. File Management
|
|
|
|
- File browser with preview
|
|
- Upload support (50GB max)
|
|
- Web scraping
|
|
- Export/import
|
|
|
|
### 6. Skills System
|
|
|
|
- Pi agent skills
|
|
- Custom task templates
|
|
- Reusable automation
|
|
|
|
### 7. Settings & Configuration
|
|
|
|
- User preferences
|
|
- System configuration
|
|
- Integration settings
|
|
- Resource limits
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
### Connection Pooling
|
|
PostgreSQL connections are managed by Drizzle ORM with configurable pool size.
|
|
|
|
### Caching
|
|
- Redis support for session data (optional)
|
|
- Database query result caching
|
|
- Static asset caching
|
|
|
|
### Rate Limiting
|
|
- Per-user API limits (todo)
|
|
- WebSocket message throttling
|
|
- File upload size limits (50GB max)
|
|
|
|
### Database Optimization
|
|
- Proper indexing on foreign keys
|
|
- Query optimization with relations
|
|
- Pagination for large datasets
|
|
|
|
---
|
|
|
|
## Security
|
|
|
|
### CORS
|
|
```typescript
|
|
honoServer.use(cors({
|
|
origin: '*',
|
|
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
allowHeaders: ['Content-Type', 'Authorization'],
|
|
}));
|
|
```
|
|
|
|
### JWT Security
|
|
- Secret stored in env variables
|
|
- Token expiration enforced
|
|
- Token blacklist for revocation
|
|
- jti (JWT ID) for tracking
|
|
|
|
### Password Security
|
|
- Argon2 hashing (modern, secure)
|
|
- No plain-text storage
|
|
- Email verification required
|
|
- Password reset via email
|
|
|
|
### WebAuthn/FIDO2
|
|
- Hardware key support
|
|
- Phishing-resistant
|
|
- No passwords stored for passkeys
|
|
|
|
---
|
|
|
|
## Monitoring & Logging
|
|
|
|
### Current Logging
|
|
- Console.log for debugging
|
|
- Error stack traces printed to console
|
|
|
|
### Future Monitoring (Signoz Integration)
|
|
- Distributed tracing
|
|
- Performance monitoring
|
|
- Error tracking
|
|
- Log aggregation
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Current Test Setup
|
|
- `@testing-library/react` for component tests
|
|
- `@playwright/test` for E2E tests
|
|
- `happy-dom` for DOM testing
|
|
|
|
### Test Command
|
|
```bash
|
|
# Not yet fully configured
|
|
bun test
|
|
```
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- **Frontend Architecture**: See `OFFICERDEV_FRONTEND.md`
|
|
- **CONVENTIONS.md**: Detailed code patterns and style guide
|
|
- **CLAUDE.md**: Project overview and patterns
|
|
- **Each folder CLAUDE.md**: Feature-specific documentation
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
Officer's backend is a modern, type-safe Bun monorepo with:
|
|
- RESTful API using Hono framework
|
|
- Real-time capabilities via WebSockets
|
|
- PostgreSQL persistence with Drizzle ORM
|
|
- AI integration through Pi coding agent
|
|
- Modular architecture with shared workspaces
|
|
- Strong TypeScript support throughout
|
|
- Robust authentication with JWT and WebAuthn
|
|
|
|
The architecture prioritizes developer experience, type safety, and maintainability while providing powerful real-time and AI-powered features to users.
|