From 70ff344d1f3aa2f8f65b2c474878a3c348e28929 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 1 Mar 2026 16:08:40 -0600 Subject: [PATCH] fix: logs page wired to activity_logs, interceptor optional workspaceId, autoRefresh on --- .claude/worktrees/agent-a56bac50 | 1 + .worktrees/feat-ms21-ui-teams-rbac | 1 + .worktrees/feat-ms22-openclaw-gateway-module | 1 + .../web/src/app/(authenticated)/logs/page.tsx | 5 +- docs/research/00-SUMMARY.md | 166 ++++ .../01-chat-orchestration-research.md | 721 ++++++++++++++++++ .../02-widgets-usage-config-research.md | 465 +++++++++++ docs/research/03-security-fleet-synthesis.md | 163 ++++ 8 files changed, 1521 insertions(+), 2 deletions(-) create mode 160000 .claude/worktrees/agent-a56bac50 create mode 160000 .worktrees/feat-ms21-ui-teams-rbac create mode 160000 .worktrees/feat-ms22-openclaw-gateway-module create mode 100644 docs/research/00-SUMMARY.md create mode 100644 docs/research/01-chat-orchestration-research.md create mode 100644 docs/research/02-widgets-usage-config-research.md create mode 100644 docs/research/03-security-fleet-synthesis.md diff --git a/.claude/worktrees/agent-a56bac50 b/.claude/worktrees/agent-a56bac50 new file mode 160000 index 0000000..c15456a --- /dev/null +++ b/.claude/worktrees/agent-a56bac50 @@ -0,0 +1 @@ +Subproject commit c15456a7791c096d3bad562ffef5828bf57b7e54 diff --git a/.worktrees/feat-ms21-ui-teams-rbac b/.worktrees/feat-ms21-ui-teams-rbac new file mode 160000 index 0000000..c640d22 --- /dev/null +++ b/.worktrees/feat-ms21-ui-teams-rbac @@ -0,0 +1 @@ +Subproject commit c640d2239473ceab5c7f91c77ec1212e6f4f99fb diff --git a/.worktrees/feat-ms22-openclaw-gateway-module b/.worktrees/feat-ms22-openclaw-gateway-module new file mode 160000 index 0000000..b13ff68 --- /dev/null +++ b/.worktrees/feat-ms22-openclaw-gateway-module @@ -0,0 +1 @@ +Subproject commit b13ff68e224da294ad56e8cb66acd780c971cac1 diff --git a/apps/web/src/app/(authenticated)/logs/page.tsx b/apps/web/src/app/(authenticated)/logs/page.tsx index 2c1a879..c6d44bd 100644 --- a/apps/web/src/app/(authenticated)/logs/page.tsx +++ b/apps/web/src/app/(authenticated)/logs/page.tsx @@ -160,8 +160,9 @@ export default function LogsPage(): ReactElement { filters.entityType = entityFilter; } - const data = await fetchActivityLogs(filters); - setActivities(data); + const response: Awaited> = + await fetchActivityLogs(filters); + setActivities(response); setError(null); } catch (err: unknown) { console.error("[Logs] Failed to fetch activity logs:", err); diff --git a/docs/research/00-SUMMARY.md b/docs/research/00-SUMMARY.md new file mode 100644 index 0000000..608aa70 --- /dev/null +++ b/docs/research/00-SUMMARY.md @@ -0,0 +1,166 @@ +# Mosaic Stack — Fast-Track Completion Plan + +**Date:** 2026-03-01 +**Goal:** Make Mosaic Stack usable for daily agent orchestration in hours, not weeks. + +Based on research of 9 community dashboards (openclaw-dashboard, clawd-control, claw-dashboard, ai-maestro, clawview, clawde-dashboard, agent-web-ui, cogni-flow, openclaw-panel), here is the prioritized build plan. + +--- + +## What Mosaic Stack Already Has (Strengths) + +- ✅ Better Auth with CSRF + bearer token bypass for API agents +- ✅ NestJS API with PostgreSQL (Prisma), full RBAC +- ✅ Next.js 15 web app: dashboard widgets, projects, kanban, calendar, tasks, knowledge, files, logs, terminal (xterm.js+WebSocket), usage tracking, settings +- ✅ Agent fleet: agents table, orchestrator endpoint, container lifecycle +- ✅ Fleet settings: LLM provider config, agent config + +## What's Missing (Gaps) + +- ❌ Chat page is a stub — not connected to any backend +- ❌ No memory/file viewer for agent workspace files +- ❌ No cron/automation visibility +- ❌ No agent creation wizard — must use DB directly +- ❌ Fleet overview lacks real-time status and health indicators +- ❌ No rate limiting or audit logging +- ❌ No agent-to-agent messaging + +--- + +## P0 — Do Today (< 2h each, unblocks daily use) + +### 1. Connect Chat to Backend +- **Why:** Chat page exists but does nothing. This is the #1 interaction surface for agents. Without it, Mosaic Stack is a dashboard you look at, not a tool you use. +- **Effort:** 2h +- **Inspired by:** ai-maestro (agent inbox), clawview (embedded chat) +- **Approach:** Wire existing chat UI to WebSocket endpoint. Send messages to agent, display responses. Use existing auth context for user identity. Store messages in PostgreSQL. + +### 2. Fleet Overview with Live Status +- **Why:** Can't tell which agents are running, idle, or broken. Every dashboard researched puts this front and center. +- **Effort:** 2h +- **Inspired by:** clawd-control (card grid), openclaw-dashboard (sparklines) +- **Approach:** Agent card grid on fleet page. Each card: name, emoji, status dot (green/yellow/red), last activity, session count. Poll agent health endpoint every 10s. Use existing agents table. + +### 3. Agent Memory/File Viewer +- **Why:** Debugging agents requires reading MEMORY.md, HEARTBEAT.md, daily logs. Without this, you SSH into the server every time. +- **Effort:** 1-2h +- **Inspired by:** openclaw-dashboard (memory viewer with markdown rendering) +- **Approach:** NestJS endpoint reads files from agent workspace dir. Path traversal protection. Next.js page: file tree sidebar + markdown preview panel. Read-only initially. + +### 4. Rate Limiting + Security Headers +- **Why:** Any exposed web app without rate limiting is a brute-force target. 30 minutes of work prevents real attacks. +- **Effort:** 30min +- **Inspired by:** openclaw-dashboard (5-attempt lockout, HSTS, CSP) +- **Approach:** Add `@nestjs/throttler` to auth endpoints (5 req/min for login). Add `helmet` middleware for security headers. + +### 5. Activity Feed / Recent Events +- **Why:** "What happened while I was away?" is the first question every morning. Every dashboard has this. +- **Effort:** 1h +- **Inspired by:** openclaw-dashboard (live feed via SSE), clawd-control (fleet activity) +- **Approach:** Query recent log entries from DB. Display as reverse-chronological list on dashboard. Agent name + action + timestamp. Auto-refresh every 30s. + +--- + +## P1 — Do This Week (2-8h each, major features) + +### 6. Agent Creation Wizard +- **Why:** Creating agents currently requires direct DB manipulation. Friction kills adoption. +- **Effort:** 3-4h +- **Inspired by:** clawd-control (guided wizard), ai-maestro (UI-based agent creation) +- **Approach:** Dialog/wizard in fleet settings: name, emoji, model, connection details (host/port/token), workspace path. Writes to agents table. Could be single-page form (faster) or multi-step (nicer UX). + +### 7. Cron/Automation Management +- **Why:** Scheduled tasks are invisible — you don't know what's running, when, or if it failed. +- **Effort:** 2-3h +- **Inspired by:** openclaw-dashboard (cron list with toggle/trigger) +- **Approach:** NestJS reads scheduled jobs (from @nestjs/schedule or config). API: list, toggle, trigger. Frontend: table with Name | Schedule | Status | Last Run | Actions. + +### 8. Audit Logging +- **Why:** Security compliance and debugging. "Who did what, when?" is unanswerable without this. +- **Effort:** 2-3h +- **Inspired by:** openclaw-dashboard (audit.log with auto-rotation) +- **Approach:** NestJS middleware logs auth events, destructive actions, config changes to audit_logs table. View in Settings > Security. + +### 9. Agent-to-Agent Simple Messaging +- **Why:** Orchestrating multiple agents requires passing context between them. Without messaging, the human is the bottleneck. +- **Effort:** 4-6h +- **Inspired by:** ai-maestro (AMP protocol — simplified) +- **Approach:** `messages` table in PostgreSQL: fromAgentId, toAgentId, type, priority, subject, body, threadId, readAt. API endpoints for send/list/read. Agent inbox UI. Skip cryptographic signing and multi-machine for now. + +### 10. SSE for Real-Time Fleet Updates +- **Why:** Polling is fine initially but SSE gives instant feedback when agents change state. +- **Effort:** 2-3h +- **Inspired by:** openclaw-dashboard, clawd-control (both use SSE) +- **Approach:** NestJS SSE endpoint streams agent status changes. Next.js EventSource client updates fleet cards in real-time. + +--- + +## P2 — Nice to Have (8h+, polish) + +### 11. TOTP Multi-Factor Authentication +- **Effort:** 4-6h +- **Inspired by:** openclaw-dashboard +- **Approach:** Better Auth may have a TOTP plugin. Otherwise use `otplib` + QR code generation. + +### 12. Multi-Machine Agent Mesh +- **Effort:** 16h+ +- **Inspired by:** ai-maestro (peer mesh, no central server) +- **Approach:** Agent discovery across machines. Network-aware routing. Defer until single-machine is solid. + +### 13. Code Graph / Codebase Visualization +- **Effort:** 12h+ +- **Inspired by:** ai-maestro (interactive code graph with delta indexing) +- **Approach:** Use ts-morph to parse codebase, D3.js for visualization. Cool but not urgent. + +### 14. Activity Heatmap +- **Effort:** 4h +- **Inspired by:** openclaw-dashboard (30-day heatmap) +- **Approach:** GitHub-style contribution heatmap showing agent activity by hour/day. + +### 15. Agent Personality Profiles +- **Effort:** 2-3h +- **Inspired by:** ai-maestro (avatars, personality, visual identity) +- **Approach:** Add personality/system-prompt field to agent config. Avatar upload. Nice for team feel. + +--- + +## Execution Order (Recommended) + +``` +Day 1 (Today): + Morning: #4 Rate limiting (30min) → #2 Fleet overview (2h) + Afternoon: #1 Connect chat (2h) → #3 Memory viewer (1.5h) + Evening: #5 Activity feed (1h) + +Day 2-3: + #6 Agent creation wizard (3h) + #7 Cron management (2h) + #8 Audit logging (2h) + +Day 4-5: + #9 Agent messaging (5h) + #10 SSE real-time (2h) + +Week 2+: + P2 items as time permits +``` + +## Total Effort to "Usable Daily" + +| Priority | Items | Total Hours | +|----------|-------|-------------| +| P0 | 5 items | ~7h | +| P1 | 5 items | ~15h | +| P2 | 5 items | ~40h+ | + +**Bottom line:** ~7 hours of focused work today gets Mosaic Stack from "demo" to "daily driver." Another 15 hours this week makes it genuinely powerful. The P2 items are polish — nice but not blocking daily use. + +--- + +## Key Design Principles (Learned from Research) + +1. **Simplicity first** (clawd-control) — No build tools for simple features. Use what's already there. +2. **Single-screen overview** (all dashboards) — Users want one page that answers "is everything OK?" +3. **Read before write** (openclaw-dashboard) — Memory viewer is read-only first, edit later. +4. **Progressive enhancement** — Polling → SSE → WebSocket. Don't over-engineer day one. +5. **Existing infra** — PostgreSQL, NestJS, Next.js are already set up. Don't add new databases or frameworks. diff --git a/docs/research/01-chat-orchestration-research.md b/docs/research/01-chat-orchestration-research.md new file mode 100644 index 0000000..affd64e --- /dev/null +++ b/docs/research/01-chat-orchestration-research.md @@ -0,0 +1,721 @@ +# Chat Interface + Task Orchestration Research Report + +**Date:** 2026-03-01 +**Focus:** Analysis of Mission Control and Clawtrol for Mosaic Stack feature development +**Goal:** Extract actionable design patterns for chat, task dispatch, and live event feeds + +--- + +## Executive Summary + +Both Mission Control and Clawtrol are OpenClaw-compatible dashboards with complementary strengths: + +| Feature | Mission Control | Clawtrol | Mosaic Stack Gap | +|---------|----------------|----------|------------------| +| Chat with agents | ❌ No direct chat | ✅ Full session chat + send | **HIGH** - Stub exists, not wired | +| Task dispatch | ✅ AI planning + Kanban | ✅ Simple Kanban | Medium - Kanban exists | +| Live events | ✅ SSE-based feed | ❌ Polling only | Medium - SSE polling exists | +| Session viewer | ❌ No | ✅ Full transcript view | **HIGH** - Missing | +| Agent management | ✅ Auto-create agents | ❌ Basic list | Medium | + +**Top 3 Quick Wins for Mosaic Stack:** +1. **Session chat interface** (< 4 hours) - Wire existing chat stub to OpenClaw API +2. **Session list view** (< 2 hours) - Read `sessions.json` + `.jsonl` transcripts +3. **Task card planning indicator** (< 1 hour) - Add purple pulse animation + +--- + +## 1. Chat Interface Analysis + +### Clawtrol Sessions Module (Best Reference) + +**File:** `src/components/modules/SessionsModule/index.tsx` + +**Key Architecture:** +```typescript +// Session list fetched from OpenClaw +const res = await fetch('/api/sessions'); +const data = await res.json(); +setSessions(data.sessions || []); + +// Session detail with message history +const res = await fetch(`/api/sessions/${encodeURIComponent(session.key)}?limit=50`); +const data = await res.json(); +setChatMessages(data.messages || []); + +// Send message to session (via Telegram or direct) +await fetch('/api/sessions/send', { + method: 'POST', + body: JSON.stringify({ sessionKey: selectedSession.key, message: msg }), +}); +``` + +**UI Pattern - Two-Column Chat Layout:** +```tsx +// Session list view +
+ {sessions.map(session => ( +
openSessionChat(session)}> + {/* Activity indicator */} +
+ + {/* Session metadata */} + {session.messageCount} msgs · {session.totalTokens}k tokens + ${session.estimatedCost.toFixed(2)} + + {/* Last message preview */} +
+ {session.lastMessages[0]?.text?.slice(0, 100)} +
+
+ ))} +
+``` + +**Chat View Pattern:** +```tsx +// Messages container with auto-scroll +
+ {chatMessages.map(msg => ( +
+
+ {/* Role badge */} + + {msg.role === 'user' ? 'you' : 'assistant'} + + + {/* Markdown content */} +
{renderMarkdown(msg.text)}
+
+
+ ))} +
{/* Auto-scroll anchor */} +
+ +// Input with Enter to send + e.key === 'Enter' && sendChatMessage()} /> +``` + +**Session API Pattern (`/api/sessions/route.ts`):** +```typescript +// Priority: CLI > Index file > Direct file scan +const SESSIONS_INDEX = join(os.homedir(), '.openclaw', 'agents', 'main', 'sessions', 'sessions.json'); +const SESSIONS_DIR = join(os.homedir(), '.openclaw', 'agents', 'main', 'sessions'); + +// Read sessions from index +const sessionsMap = JSON.parse(await readFile(SESSIONS_INDEX, 'utf-8')); + +// Enrich with message count and last messages +for (const session of sessions) { + const [msgs, count] = await Promise.all([ + getLastMessages(sessionFile, 3), // Last 3 messages + getMessageCount(sessionFile), // Total count + ]); +} + +// Parse JSONL for messages +function getLastMessages(sessionFile: string, count: number) { + const lines = data.trim().split('\n').filter(Boolean); + for (let i = lines.length - 1; i >= 0 && messages.length < count; i--) { + const parsed = JSON.parse(lines[i]); + if (parsed.type === 'message' && parsed.message) { + messages.unshift({ + role: parsed.message.role, + text: extractTextFromContent(parsed.message.content), + timestamp: parsed.timestamp, + }); + } + } +} +``` + +**Message Send Pattern (`/api/sessions/send/route.ts`):** +```typescript +// Parse session key to determine target +function parseSessionKey(key: string): { chatId: string; topicId?: string } | null { + // agent:main:main → DM to owner + if (key === 'agent:main:main') { + return { chatId: await getDefaultChatId() }; + } + + // agent:main:telegram:group::topic: + const topicMatch = key.match(/:group:(-?\d+):topic:(\d+)$/); + if (topicMatch) { + return { chatId: topicMatch[1], topicId: topicMatch[2] }; + } +} + +// Send via Telegram Bot API (or could use OpenClaw chat.send) +const res = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, { + method: 'POST', + body: JSON.stringify({ chat_id: target.chatId, text: message }), +}); +``` + +### Key Takeaways for Mosaic Stack + +1. **Session key format:** `agent:main:telegram:group::topic:` or `agent:main:main` +2. **JSONL parsing:** Read from `~/.openclaw/agents/main/sessions/.jsonl` +3. **Cost estimation:** + ```typescript + const isOpus = modelName.includes('opus'); + const inputRate = isOpus ? 15 : 3; + const outputRate = isOpus ? 75 : 15; + const cost = (inputTokens / 1_000_000 * inputRate) + (outputTokens / 1_000_000 * outputRate); + ``` +4. **Activity color logic:** + ```typescript + if (lastActivity > hourAgo) return 'green'; // Active + if (lastActivity > dayAgo) return 'yellow'; // Recent + return 'dim'; // Stale + ``` + +--- + +## 2. Task/Agent Dispatch Flow (Mission Control) + +### AI Planning UX Pattern + +**The Flow:** +``` +CREATE → PLAN (AI Q&A) → ASSIGN (Auto-agent) → EXECUTE → DELIVER +``` + +**Status Columns:** +``` +PLANNING → INBOX → ASSIGNED → IN PROGRESS → TESTING → REVIEW → DONE +``` + +**PlanningTab.tsx - Core Pattern:** + +1. **Start Planning Button:** +```tsx +if (!state?.isStarted) { + return ( + + ); +} +``` + +2. **Question/Answer Loop:** +```tsx +// Current question display +

{state.currentQuestion.question}

+ +// Multiple choice options +{state.currentQuestion.options.map(option => ( + +))} + +// "Other" option with text input +{isOther && isSelected && ( + +)} +``` + +3. **Polling for AI Response:** +```typescript +// Poll every 2 seconds for next question +pollingIntervalRef.current = setInterval(() => { + pollForUpdates(); +}, 2000); + +// 90-second timeout +pollingTimeoutRef.current = setTimeout(() => { + setError('Taking too long to respond...'); +}, 90000); +``` + +4. **Planning Complete - Spec Display:** +```tsx +if (state?.isComplete && state?.spec) { + return ( +
+
+ Planning Complete +
+ + {/* Generated spec */} +
+

{state.spec.title}

+

{state.spec.summary}

+
    {state.spec.deliverables.map(d =>
  • {d}
  • )}
+
    {state.spec.success_criteria.map(c =>
  • {c}
  • )}
+
+ + {/* Auto-created agents */} + {state.agents.map(agent => ( +
+ {agent.avatar_emoji} +
+

{agent.name}

+

{agent.role}

+
+
+ ))} +
+ ); +} +``` + +### Planning API Pattern + +**POST `/api/tasks/[id]/planning` - Start Planning:** +```typescript +// Create session key +const sessionKey = `agent:main:planning:${taskId}`; + +// Build planning prompt +const planningPrompt = ` +PLANNING REQUEST + +Task Title: ${task.title} +Task Description: ${task.description} + +Generate your FIRST question. Respond with ONLY valid JSON: +{ + "question": "Your question here?", + "options": [ + {"id": "A", "label": "First option"}, + {"id": "B", "label": "Second option"}, + {"id": "other", "label": "Other"} + ] +} +`; + +// Send to OpenClaw +await client.call('chat.send', { + sessionKey, + message: planningPrompt, +}); + +// Store in DB +UPDATE tasks SET planning_session_key = ?, planning_messages = ?, status = 'planning' +``` + +**Key Insight:** The AI doesn't just plan - it asks **multiple-choice questions** to clarify requirements. This is the "AI clarification before dispatch" pattern. + +### Kanban Card with Planning Indicator + +```tsx +// TaskCard.tsx +const isPlanning = task.status === 'planning'; + +
+ + {isPlanning && ( +
+
+ Continue planning +
+ )} +
+``` + +### Auto-Dispatch Pattern + +```typescript +// When task moves from PLANNING → INBOX (planning complete) +if (shouldTriggerAutoDispatch(oldStatus, newStatus, agentId)) { + await triggerAutoDispatch({ + taskId, + taskTitle, + agentId, + agentName, + workspaceId, + }); +} +``` + +--- + +## 3. Live Event Feed + +### Mission Control SSE Pattern + +**`src/lib/events.ts`:** +```typescript +// In-memory client registry +const clients = new Set(); + +export function registerClient(controller) { + clients.add(controller); +} + +export function broadcast(event: SSEEvent) { + const data = `data: ${JSON.stringify(event)}\n\n`; + const encoded = new TextEncoder().encode(data); + + for (const client of Array.from(clients)) { + try { + client.enqueue(encoded); + } catch { + clients.delete(client); + } + } +} +``` + +**LiveFeed Component:** +```tsx +// Filter tabs +
+ {['all', 'tasks', 'agents'].map(tab => ( + + ))} +
+ +// Event list with icons +{filteredEvents.map(event => ( +
+ {getEventIcon(event.type)} +

{event.message}

+ {formatDistanceToNow(event.created_at)} +
+))} + +// Event icons +function getEventIcon(type: string) { + switch (type) { + case 'task_created': return '📋'; + case 'task_assigned': return '👤'; + case 'task_completed': return '✅'; + case 'message_sent': return '💬'; + case 'agent_joined': return '🎉'; + } +} +``` + +### SSE vs WebSocket Trade-off + +| Aspect | SSE (Mission Control) | WebSocket (Clawtrol) | +|--------|----------------------|---------------------| +| Direction | Server → Client only | Bidirectional | +| Reconnect | Automatic browser handling | Manual implementation | +| Overhead | HTTP-based, lighter | Full TCP connection | +| Use case | Event feeds, notifications | Real-time terminal, chat | + +**Recommendation:** Use SSE for event feeds (simpler), WebSocket for interactive terminals. + +--- + +## 4. Session Viewer Pattern + +### Clawtrol Session List + +```tsx +// Session card with activity indicator +
openSessionChat(session)}> + {/* Activity dot */} +
+ + {/* Session info */} +

{session.label}

+
+ {session.messageCount} msgs · {session.totalTokens}k tokens + {session.estimatedCost > 0 && · ${session.estimatedCost.toFixed(2)}} + {session.model && · {session.model}} +
+ + {/* Last message preview */} + {session.lastMessages?.length > 0 && ( +
+ {session.lastMessages[0]?.role === 'user' ? 'you: ' : 'assistant: '} + {session.lastMessages[0]?.text?.slice(0, 100)} +
+ ))} +
+``` + +### Session Label Mapping + +```typescript +const TOPIC_NAMES: Record = { + '1369': '🔖 Bookmarks', + '13': '🌴 Bali Trip', + '14': '💰 Expenses', + // ... user-defined topic labels +}; + +function getSessionLabel(key: string): string { + if (key === 'agent:main:main') return 'Main Session (DM)'; + if (key.includes(':subagent:')) return `Subagent ${uuid.slice(0, 8)}`; + + // Telegram topic + const topicMatch = key.match(/:topic:(\d+)$/); + if (topicMatch) { + return TOPIC_NAMES[topicMatch[1]] || `Topic ${topicMatch[1]}`; + } + + return key.split(':').pop() || key; +} +``` + +--- + +## 5. OpenClaw Client Integration + +### WebSocket Client Pattern + +**`src/lib/openclaw/client.ts`:** +```typescript +export class OpenClawClient extends EventEmitter { + private ws: WebSocket | null = null; + private pendingRequests = new Map(); + private connected = false; + private authenticated = false; + + async connect(): Promise { + // Add token to URL for auth + const wsUrl = new URL(this.url); + wsUrl.searchParams.set('token', this.token); + + this.ws = new WebSocket(wsUrl.toString()); + + this.ws.onmessage = (event) => { + const data = JSON.parse(event.data); + + // Handle challenge-response auth + if (data.type === 'event' && data.event === 'connect.challenge') { + const response = { + type: 'req', + id: crypto.randomUUID(), + method: 'connect', + params: { + auth: { token: this.token }, + role: 'operator', + scopes: ['operator.admin'], + } + }; + this.ws.send(JSON.stringify(response)); + return; + } + + // Handle RPC responses + if (data.type === 'res') { + const pending = this.pendingRequests.get(data.id); + if (pending) { + data.ok ? pending.resolve(data.payload) : pending.reject(data.error); + } + } + }; + } + + async call(method: string, params?: object): Promise { + const id = crypto.randomUUID(); + const message = { type: 'req', id, method, params }; + + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { resolve, reject }); + this.ws.send(JSON.stringify(message)); + + // 30s timeout + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id); + reject(new Error(`Timeout: ${method}`)); + } + }, 30000); + }); + } + + // Convenience methods + async listSessions() { return this.call('sessions.list'); } + async sendMessage(sessionId: string, content: string) { + return this.call('sessions.send', { session_id: sessionId, content }); + } + async listAgents() { return this.call('agents.list'); } +} +``` + +### Event Deduplication Pattern + +```typescript +// Global dedup cache (survives Next.js hot reload) +const GLOBAL_EVENT_CACHE_KEY = '__openclaw_processed_events__'; +const globalProcessedEvents = globalThis[GLOBAL_EVENT_CACHE_KEY] || new Map(); + +// Content-based event ID +function generateEventId(data: any): string { + const canonical = JSON.stringify({ + type: data.type, + seq: data.seq, + runId: data.payload?.runId, + payloadHash: createHash('sha256').update(JSON.stringify(data.payload)).digest('hex').slice(0, 16), + }); + return createHash('sha256').update(canonical).digest('hex').slice(0, 32); +} + +// Skip duplicates +if (globalProcessedEvents.has(eventId)) return; +globalProcessedEvents.set(eventId, Date.now()); + +// LRU cleanup +if (globalProcessedEvents.size > MAX_EVENTS) { + // Remove oldest entries +} +``` + +--- + +## 6. Feature Recommendations for Mosaic Stack + +### Quick Wins (< 4 hours each) + +| Feature | Effort | Impact | Source | +|---------|--------|--------|--------| +| **Session list page** | 2h | HIGH | Clawtrol | +| **Session chat interface** | 4h | HIGH | Clawtrol | +| **Planning indicator on task cards** | 1h | MEDIUM | Mission Control | +| **Activity dots (green/yellow/dim)** | 30m | MEDIUM | Clawtrol | +| **Token/cost display per session** | 1h | MEDIUM | Clawtrol | +| **Event feed filter tabs** | 1h | LOW | Mission Control | + +### Medium Effort (4-16 hours) + +| Feature | Effort | Impact | Description | +|---------|--------|--------|-------------| +| **AI planning flow** | 8h | HIGH | Multi-choice Q&A before dispatch | +| **OpenClaw WebSocket client** | 4h | HIGH | Real-time event streaming | +| **Session transcript viewer** | 4h | MEDIUM | JSONL parsing + display | +| **Auto-agent creation** | 8h | MEDIUM | Generate agents from planning spec | + +### Architecture Recommendations + +1. **Keep SSE for event feed** - Simpler than WebSocket for one-way updates +2. **Use OpenClaw `chat.send` for messages** - Don't implement Telegram API directly +3. **Store session metadata in PostgreSQL** - Mirror `sessions.json` for joins +4. **Implement planning as a state machine** - Clear states: idle → started → questioning → complete + +--- + +## 7. Code Snippets to Reuse + +### Session API Route (Clawtrol-style) + +```typescript +// app/api/sessions/route.ts +import { readFile, readdir } from 'fs/promises'; +import { join } from 'path'; +import os from 'os'; + +const SESSIONS_DIR = join(os.homedir(), '.openclaw', 'agents', 'main', 'sessions'); + +export async function GET() { + // Try CLI first + try { + const { stdout } = await execAsync('openclaw sessions --json'); + return NextResponse.json({ sessions: JSON.parse(stdout).sessions, source: 'cli' }); + } catch {} + + // Fallback to file + const index = await readFile(join(SESSIONS_DIR, 'sessions.json'), 'utf-8'); + const sessionsMap = JSON.parse(index); + + const sessions = await Promise.all( + Object.entries(sessionsMap).map(async ([key, data]) => ({ + key, + label: getSessionLabel(key), + kind: getSessionKind(key), + lastActivity: new Date(data.updatedAt).toISOString(), + messageCount: await getMessageCount(key), + totalTokens: data.totalTokens || 0, + estimatedCost: calculateCost(data), + })) + ); + + return NextResponse.json({ sessions, source: 'file' }); +} +``` + +### Activity Indicator Component + +```tsx +// components/ActivityIndicator.tsx +export function ActivityIndicator({ lastActivity }: { lastActivity: Date }) { + const now = Date.now(); + const hourAgo = now - 60 * 60 * 1000; + const dayAgo = now - 24 * 60 * 60 * 1000; + + const color = lastActivity.getTime() > hourAgo + ? 'bg-green-500' + : lastActivity.getTime() > dayAgo + ? 'bg-yellow-500' + : 'bg-gray-500'; + + const glow = lastActivity.getTime() > hourAgo + ? 'shadow-[0_0_6px_rgba(34,197,94,0.5)]' + : ''; + + return ( +
+ ); +} +``` + +### Cost Estimation Utility + +```typescript +// lib/cost-estimation.ts +const RATES = { + opus: { input: 15, output: 75 }, + sonnet: { input: 3, output: 15 }, + haiku: { input: 0.25, output: 1.25 }, +}; + +export function estimateCost(model: string, inputTokens: number, outputTokens: number): number { + const tier = model.includes('opus') ? 'opus' + : model.includes('sonnet') ? 'sonnet' + : 'haiku'; + + const rates = RATES[tier]; + return (inputTokens / 1_000_000 * rates.input) + + (outputTokens / 1_000_000 * rates.output); +} +``` + +--- + +## 8. Summary + +**Best patterns to steal:** + +1. **Clawtrol's session chat** - Clean two-panel layout with activity dots +2. **Mission Control's planning flow** - Multi-choice Q&A with polling +3. **Clawtrol's JSONL parsing** - Efficient reverse-iteration for last N messages +4. **Mission Control's SSE events** - Simple broadcast pattern with client registry +5. **Activity color logic** - Hour = green, day = yellow, older = dim + +**Don't copy:** + +1. Telegram Bot API integration - Use OpenClaw `chat.send` instead +2. File-based session index - Mosaic Stack has PostgreSQL +3. PM2 daemon management - Use Docker/systemd + +**Next steps:** + +1. Create `/app/(dashboard)/sessions` page with session list +2. Add chat view at `/app/(dashboard)/sessions/[key]` +3. Wire `/api/sessions` route to OpenClaw CLI or sessions.json +4. Add `ActivityIndicator` component to session cards +5. Add "Start Planning" button to task cards in Kanban diff --git a/docs/research/02-widgets-usage-config-research.md b/docs/research/02-widgets-usage-config-research.md new file mode 100644 index 0000000..b0cdae0 --- /dev/null +++ b/docs/research/02-widgets-usage-config-research.md @@ -0,0 +1,465 @@ +# Widget Layouts + Usage Tracking + Config Management Research + +**Date:** 2026-03-01 +**Sources:** +- [LobsterBoard](https://github.com/Curbob/LobsterBoard) — 50+ drag-and-drop widgets, SSE, layout templates +- [VidClaw](https://github.com/madrzak/vidclaw) — Soul/config editor, usage tracking, skills manager + +**Target:** Mosaic Stack (Next.js 15 / React 19 / NestJS / shadcn/ui / PostgreSQL) + +--- + +## Executive Summary + +| Feature | LobsterBoard | VidClaw | Mosaic Stack Current | Quick Win? | +|---------|--------------|---------|---------------------|------------| +| Drag-and-drop widgets | ✅ Full | — | ⚠️ WidgetGrid exists, needs enabling | **Yes (30min)** | +| Layout persistence | ✅ JSON to server | — | ✅ API + DB | Done | +| SSE real-time | ✅ System stats | — | ✅ Already implemented | Done | +| Usage widget (header) | — | ✅ Compact popover | ❌ Full page only | **Yes (30min)** | +| Token parsing | — | ✅ JSONL session files | ⚠️ API-based | Low priority | +| Soul/config editor | — | ✅ Multi-file + history | ❌ Not in UI | **Yes (1-2h)** | +| Skills manager | — | ✅ Full CRUD + toggle | ❌ Not in UI | **Yes (1-2h)** | +| Templates | ✅ Layout presets | ✅ Soul templates | ❌ None | Medium | + +--- + +## 1. Widget System (LobsterBoard) + +### Widget Registry Pattern + +LobsterBoard uses a global `WIDGETS` object where each widget is self-contained: + +```javascript +const WIDGETS = { + 'weather': { + name: 'Local Weather', + icon: '🌡️', + category: 'small', // 'small' | 'large' | 'layout' + description: 'Shows current weather...', + defaultWidth: 200, + defaultHeight: 120, + hasApiKey: false, + properties: { // User-configurable defaults + title: 'Local Weather', + location: 'Atlanta', + units: 'F', + refreshInterval: 600 + }, + preview: `
...
`, + generateHtml: (props) => `...`, + generateJs: (props) => `...` + }, + // 50+ more widgets +}; +``` + +**Key patterns:** +1. **Widget as code generator** — Each widget produces its own HTML + JS at render time +2. **Shared SSE** — System stats widgets share one `EventSource('/api/stats/stream')` with a callback registry +3. **Edit/View mode toggle** — Widget JS stops in edit mode, resumes in view mode +4. **20px grid snapping** — All positions snap to grid during drag +5. **Icon theming** — Dual emoji + Phosphor icon map per widget type + +### Layout Persistence Schema + +```json +{ + "canvas": { "width": 1920, "height": 1080 }, + "fontScale": 1.0, + "widgets": [ + { + "id": "widget-1", + "type": "weather", + "x": 20, "y": 40, + "width": 200, "height": 120, + "properties": { "title": "Weather", "location": "Kansas City", "units": "F" } + } + ] +} +``` + +Saved via `POST /config` with `Content-Type: application/json`. Loaded on startup, starts in view mode. + +### What Mosaic Stack Already Has + +Mosaic's dashboard (`page.tsx`) already has: +- ✅ `WidgetGrid` with `react-grid-layout` +- ✅ `WidgetPlacement` type in `@mosaic/shared` +- ✅ Layout CRUD API (`fetchDefaultLayout`, `createLayout`, `updateLayout`) +- ✅ `DEFAULT_LAYOUT` for new users +- ✅ Debounced auto-save on layout change (800ms) + +**Gap:** Widget drag-and-drop may need enabling. No dynamic widget registration or per-widget config panel yet. + +### Recommendations + +| Priority | Feature | Effort | Impact | +|----------|---------|--------|--------| +| 🔴 High | Verify/enable drag-and-drop in WidgetGrid | 30min | Core UX | +| 🔴 High | Widget picker modal (add/remove) | 1h | Customization | +| 🟡 Med | Per-widget config dialog | 2h | Deeper customization | +| 🟢 Low | Layout template presets | 2h | Onboarding | + +--- + +## 2. Usage Tracking (VidClaw) + +### Backend: JSONL Session Parsing + +VidClaw's `server/controllers/usage.js` reads OpenClaw session transcript files directly: + +```javascript +export function getUsage(req, res) { + const sessionsDir = path.join(OPENCLAW_DIR, 'agents', 'main', 'sessions'); + const tz = getTimezone(); + const todayStart = startOfDayInTz(now, tz); + const weekStart = startOfWeekInTz(now, tz); + + const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl')); + for (const file of files) { + for (const line of content.split('\n').filter(Boolean)) { + const entry = JSON.parse(line); + const usage = entry.message?.usage || entry.usage; + if (usage?.cost?.total) { + const tokens = (usage.input || 0) + (usage.output || 0) + (usage.cacheRead || 0); + const cost = usage.cost.total; + // Aggregate by day/week/month... + } + } + } + + // Also: 5-hour rolling "session" window + const SESSION_LIMIT = 45_000_000; + const WEEKLY_LIMIT = 180_000_000; + + res.json({ + model: 'claude-sonnet-4-20250514', + tiers: [ + { label: 'Current session', percent: 45, resetsIn: '2h 15m', tokens: 20000000, cost: 12.50 }, + { label: 'Current week', percent: 32, resetsIn: '4d 8h', tokens: 58000000, cost: 38.20 } + ], + details: { + today: { tokens, cost, sessions }, + week: { tokens, cost, sessions }, + month: { tokens, cost, sessions } + } + }); +} +``` + +**Key design choices:** +- Multi-tier limits (session 45M + weekly 180M tokens) +- Timezone-aware day/week boundaries +- Rolling 5-hour session window +- Includes cost tracking from `usage.cost.total` + +### Frontend: Compact Header Widget + +VidClaw's `UsageWidget.tsx` is a **popover in the header bar** — not a full page: + +```tsx +export default function UsageWidget() { + const [expanded, setExpanded] = useState(false); + const { data: usage } = useUsage(); + + const sessionPct = usage?.tiers?.[0]?.percent ?? 0; + const pillColor = sessionPct > 80 ? 'text-red-400' : sessionPct > 60 ? 'text-amber-400' : 'text-emerald-400'; + + return ( +
+ + + {expanded && ( +
+ {/* Model selector */} + + {/* Progress bars per tier */} + {tiers.map(tier => )} +
+ )} +
+ ); +} +``` + +Color coding: green (<60%), amber (60-80%), red (>80%). Includes model switcher. + +### What Mosaic Stack Has + +Full usage page (430+ lines) with Recharts: line charts, bar charts, pie charts, time range selector. **But no compact header widget.** + +### Recommendations + +| Priority | Feature | Effort | Impact | +|----------|---------|--------|--------| +| 🔴 High | Compact UsageWidget in header | 30min | Always-visible usage | +| 🔴 High | Session + weekly limit % | 1h | Know quota status | +| 🟡 Med | Model switcher in popover | 30min | Quick model changes | +| 🟢 Low | JSONL parsing backend | 3h | Real-time session tracking | + +--- + +## 3. Soul/Config Editor (VidClaw) + +### Backend + +```javascript +// server/controllers/soul.js +const FILE_TABS = ['SOUL.md', 'IDENTITY.md', 'USER.md', 'AGENTS.md']; + +export function getSoul(req, res) { + const content = fs.readFileSync(path.join(WORKSPACE, 'SOUL.md'), 'utf-8'); + res.json({ content, lastModified: stat.mtime.toISOString() }); +} + +export function putSoul(req, res) { + const old = fs.readFileSync(fp, 'utf-8'); + if (old) appendHistory(histPath, old); // Auto-version on every save + fs.writeFileSync(fp, req.body.content); + res.json({ success: true }); +} + +export function getSoulHistory(req, res) { + res.json(readHistoryFile('soul-history.json')); + // Returns: [{ content, timestamp }] +} + +export function revertSoul(req, res) { + appendHistory(histPath, currentContent); // Backup before revert + fs.writeFileSync(fp, history[req.body.index].content); + res.json({ success: true, content }); +} +``` + +### Frontend + +`SoulEditor.tsx` (10KB) — full-featured editor: + +1. **File tabs** — SOUL.md, IDENTITY.md, USER.md, AGENTS.md +2. **Code editor** — Textarea with Tab support, Ctrl+S save +3. **Right sidebar** with two tabs: + - **Templates** — Pre-built soul templates, click to preview, "Use Template" to apply + - **History** — Reverse-chronological versions, click to preview, hover to show "Revert" +4. **Footer** — Char count, last modified timestamp, dirty indicator, Reset/Save buttons +5. **Dirty state** — Yellow dot on tab, "Unsaved changes" warning, confirm before switching tabs + +### Recommendations for Mosaic Stack + +| Priority | Feature | Effort | Impact | +|----------|---------|--------|--------| +| 🔴 High | Basic editor page with file tabs | 1h | Removes CLI dependency | +| 🔴 High | Save + auto-version history | 30min | Safety net for edits | +| 🟡 Med | Template sidebar | 1h | Onboarding for new users | +| 🟡 Med | Preview before apply/revert | 30min | Prevent mistakes | +| 🟢 Low | Syntax highlighting (Monaco) | 1h | Polish | + +**NestJS endpoint sketch:** +```typescript +@Controller('workspace') +export class WorkspaceController { + @Get('file') + getFile(@Query('name') name: string) { + // Validate name is in allowed list + // Read from workspace dir, return { content, lastModified } + } + + @Put('file') + putFile(@Query('name') name: string, @Body() body: { content: string }) { + // Append old content to history JSON + // Write new content + } + + @Get('file/history') + getHistory(@Query('name') name: string) { + // Return history entries + } +} +``` + +--- + +## 4. Skills Manager (VidClaw) + +### Backend: Skill Scanning + +`server/lib/skills.js` scans multiple directories for skills: + +```javascript +const SKILL_SCAN_DIRS = { + bundled: ['/opt/openclaw/skills'], + managed: ['~/.config/mosaic/skills'], + workspace: ['~/.openclaw/workspace/skills'] +}; + +export function scanSkills() { + const config = readOpenclawJson(); + const entries = config.skills?.entries || {}; // Enabled/disabled state + + for (const [source, roots] of Object.entries(SKILL_SCAN_DIRS)) { + for (const d of fs.readdirSync(rootDir, { withFileTypes: true })) { + const content = fs.readFileSync(path.join(d.name, 'SKILL.md'), 'utf-8'); + const fm = parseFrontmatter(content); // Parse YAML frontmatter + + skills.push({ + id: d.name, + name: fm.name || d.name, + description: fm.description || '', + source, // 'bundled' | 'managed' | 'workspace' + enabled: entries[id]?.enabled ?? true, + path: skillPath, + }); + } + } + return skills; +} +``` + +### Backend: CRUD + +```javascript +// Toggle: writes to openclaw.json config +export function toggleSkill(req, res) { + config.skills.entries[id] = { enabled: !current }; + writeOpenclawJson(config); +} + +// Create: writes SKILL.md with frontmatter +export function createSkill(req, res) { + const dir = path.join(SKILLS_DIRS.workspace, name); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, 'SKILL.md'), + `---\nname: ${name}\ndescription: ${desc}\n---\n\n${instructions}`); +} + +// Delete: workspace skills only +export function deleteSkill(req, res) { + if (skill.source !== 'workspace') return res.status(403); + fs.rmSync(skill.path, { recursive: true }); +} +``` + +### Frontend + +`SkillsManager.tsx` (12KB): + +1. **Stats cards** — Total, Enabled, Bundled, Workspace counts +2. **Filters** — Search, source filter dropdown, status filter dropdown +3. **Skill cards** — Name + source badge + toggle switch + expand/collapse +4. **Expanded view** — Shows full SKILL.md content (lazy-loaded) +5. **Create modal** — Name (slug), description, instructions (markdown textarea) +6. **Source badges** — Color-coded: blue=bundled, orange=managed, green=workspace +7. **Delete** — Only workspace skills, with confirmation + +### Recommendations for Mosaic Stack + +| Priority | Feature | Effort | Impact | +|----------|---------|--------|--------| +| 🔴 High | Skills list with toggle | 1h | Visibility + control | +| 🟡 Med | Create skill modal | 1h | No CLI needed | +| 🟡 Med | Skill content viewer | 30min | See what skills do | +| 🟢 Low | Search + filters | 30min | Polish for 100+ skills | + +--- + +## 5. Quick Wins — Prioritized Implementation Plan + +### 🚀 #1: Compact Usage Widget in Header (30 min) +- Create `components/UsageWidget.tsx` using shadcn `Popover` + `Progress` +- Reuse existing `useUsageSummary` hook +- Add to authenticated layout header +- Color-code: green/amber/red based on percentage + +### 🚀 #2: Enable Widget Drag-and-Drop (30 min) +- Check `WidgetGrid` for `isDraggable`/`static` props +- Enable drag + resize in react-grid-layout +- Verify auto-save still works after moves + +### 🚀 #3: Soul Editor Page (1-2h) +- New page: `settings/soul/page.tsx` +- File tabs: SOUL.md, IDENTITY.md, USER.md, AGENTS.md +- Backend: `GET/PUT /api/workspace/file?name=SOUL.md` +- Auto-version history on save +- Simple Textarea with Save button + +### 🚀 #4: Skills List + Toggle (1-2h) +- New page: `settings/skills/page.tsx` +- Backend: `GET /api/skills`, `POST /api/skills/:id/toggle` +- Scan skill directories, parse frontmatter +- Toggle switch per skill using shadcn `Switch` + +### 🚀 #5: Dashboard Empty State (30 min) +- Show "Add your first widget" card when layout is empty +- Link to widget picker + +**Total estimated effort for all 5: ~4-5 hours for a dramatically more complete UI.** + +--- + +## 6. Schemas Worth Borrowing + +### Skill Type (for Mosaic Stack shared package) +```typescript +interface Skill { + id: string; + name: string; + description: string; + source: 'bundled' | 'managed' | 'workspace'; + enabled: boolean; + path: string; +} +``` + +### Usage Tier Type +```typescript +interface UsageTier { + label: string; + percent: number; + resetsIn: string; + tokens: number; + cost: number; +} +``` + +### Widget Definition Type (if building registry) +```typescript +interface WidgetDefinition { + id: string; + name: string; + icon: string; + category: 'kpi' | 'chart' | 'list' | 'system'; + description: string; + defaultSize: { w: number; h: number }; + configSchema?: Record; + component: React.ComponentType; +} +``` + +--- + +## Key File References + +### LobsterBoard +- `js/widgets.js` — 50+ widget definitions with HTML/JS generators +- `js/builder.js` — Canvas, drag-drop, resize, edit/view mode, config save/load + +### VidClaw +- `server/controllers/usage.js` — JSONL token parsing, multi-tier limits +- `server/controllers/soul.js` — SOUL.md CRUD + version history +- `server/controllers/skills.js` — Skills CRUD (toggle, create, delete) +- `server/lib/skills.js` — Directory scanning + frontmatter parsing +- `src/components/Usage/UsageWidget.tsx` — Compact header usage popover +- `src/components/Soul/SoulEditor.tsx` — Multi-file editor with history + templates +- `src/components/Skills/SkillsManager.tsx` — Skills list, filter, toggle, create + +--- + +*Research completed 2026-03-01 by subagent for Mosaic Stack development.* diff --git a/docs/research/03-security-fleet-synthesis.md b/docs/research/03-security-fleet-synthesis.md new file mode 100644 index 0000000..1c181d9 --- /dev/null +++ b/docs/research/03-security-fleet-synthesis.md @@ -0,0 +1,163 @@ +# Security Patterns, Lightweight Monitors & Final 10% Synthesis + +**Research Date:** 2026-03-01 +**Repositories Analyzed:** +1. [tugcantopaloglu/openclaw-dashboard](https://github.com/tugcantopaloglu/openclaw-dashboard) — Security-hardened: TOTP MFA, PBKDF2, rate limiting, memory viewer, cron manager +2. [Temaki-AI/clawd-control](https://github.com/Temaki-AI/clawd-control) — Lightweight fleet monitor, auto-discovery, agent creation wizard +3. [spleck/claw-dashboard](https://github.com/spleck/claw-dashboard) — Terminal-style monitor, btop-inspired +4. [23blocks-OS/ai-maestro](https://github.com/23blocks-OS/ai-maestro) — Agent-to-agent messaging, AMP protocol, multi-machine mesh + +--- + +## 1. Memory/File Viewer (openclaw-dashboard) + +**How it works:** Reads workspace files directly from filesystem — MEMORY.md, HEARTBEAT.md, memory/YYYY-MM-DD.md. Two API endpoints: `GET /api/memory-files` (list) and `GET /api/memory-file?path=` (read content). Frontend is a simple file browser + markdown viewer. Edits create `.bak` backup files automatically. + +**Security:** Path traversal protection validates all paths stay within workspace root. Read-only by default; edit requires explicit action. + +**Simplest implementation for Mosaic Stack:** +- NestJS controller with 2 endpoints (list files, read file) +- Path validation middleware (resolve path, check it starts with workspace root) +- Next.js page: left sidebar file tree + right panel markdown render +- Use `react-markdown` for rendering (already likely in deps) +- **Effort: 1-2h** + +--- + +## 2. Cron Job Management UI (openclaw-dashboard) + +**How it works:** Reads cron jobs from `$OPENCLAW_DIR/cron/jobs.json`. Three endpoints: +- `GET /api/crons` — list all jobs with status +- `POST /api/cron/:id/toggle` — enable/disable +- `POST /api/cron/:id/run` — manually trigger + +Frontend: table with Name | Schedule | Status | Last Run | Actions columns. Toggle switches and "Run Now" buttons. + +**For Mosaic Stack:** Could be a Settings sub-tab ("Automation"). Back-end reads from DB or config file. NestJS `@nestjs/schedule` already supports cron — just need UI visibility into what's scheduled. + +**Effort: 2-3h** + +--- + +## 3. Agent Creation Wizard (clawd-control) + +**How it works:** Guided multi-step form at `create.html`. Agent config fields: +```json +{ + "id": "my-agent", + "gatewayAgentId": "main", + "name": "My Agent", + "emoji": "🤖", + "host": "127.0.0.1", + "port": 18789, + "token": "YOUR_GATEWAY_TOKEN", + "workspace": "/path/to/agent/workspace" +} +``` + +Backend provisioning logic in `create-agent.mjs`. Auto-discovery via `discover.mjs` finds local agents automatically. + +**For Mosaic Stack:** Already has agents table in DB. Add a "Create Agent" dialog/wizard with: name, type/model, emoji, connection details, workspace path. Multi-step or single form — single form is faster to build. + +**Effort: 2-4h** + +--- + +## 4. Fleet Overview UX (all dashboards) + +**What good looks like:** + +| Dashboard | Approach | Key Insight | +|-----------|----------|-------------| +| clawd-control | Grid of agent cards, single-screen | "See all agents at a glance with health indicators" | +| openclaw-dashboard | Sidebar + tabs, sparklines, heatmaps | Rich metrics: sessions, costs, rate limits | +| claw-dashboard | Terminal btop-style, 2s refresh | Lightweight, resource-efficient | +| ai-maestro | Tree view with auto-coloring | `project-backend-api` → 3-level tree | + +**Key metrics that matter:** +- Status indicator (online/offline/error) — most important +- Last activity timestamp +- Active session count +- Token usage / cost +- CPU/RAM (if host-level monitoring) +- Error count (last 24h) + +**Recommended for Mosaic Stack:** Card grid layout. Each card: emoji + name, colored status dot, last activity time, token count. Click to expand/detail. Add a "Recent Activity" feed below the grid. + +**Effort: 3-4h** + +--- + +## 5. AMP Protocol (ai-maestro) + +**What it is:** Agent Messaging Protocol — email-like communication between agents. Priority levels, message types, cryptographic signatures, push notifications. Full spec at agentmessaging.org. + +**Key concept:** "I was the human mailman between 35 agents. AMP removes the human bottleneck." + +**Worth borrowing for Mosaic Stack:** +- Simple agent-to-agent message table in PostgreSQL (already have DB) +- Priority levels (low/normal/high) +- Message types (task/notification/query) +- Thread awareness (threadId field) + +**NOT worth borrowing (yet):** +- Cryptographic signatures (overkill) +- Multi-machine mesh (premature) +- Full AMP protocol compliance (too complex) + +**Simple alternative:** Add a `messages` table to Prisma schema with fromAgentId, toAgentId, type, priority, subject, body, threadId, readAt. Poll or WebSocket for delivery. **Effort: 4-8h** + +--- + +## 6. Security Patterns Worth Adopting + +**From openclaw-dashboard (already mature in Mosaic Stack):** + +| Pattern | openclaw-dashboard | Mosaic Stack Status | Action | +|---------|-------------------|-------------------|--------| +| Password hashing | PBKDF2, 100k iterations | Better Auth handles this | ✅ Done | +| CSRF protection | N/A (session-based) | Better Auth CSRF | ✅ Done | +| RBAC | N/A | Full RBAC implemented | ✅ Done | +| Rate limiting | 5 fail → 15min lockout | Not implemented | Add NestJS throttler | +| TOTP MFA | Google Auth compatible | Not implemented | P2 — Better Auth plugin exists | +| Audit logging | All auth events logged | Not implemented | Add NestJS middleware | +| Security headers | HSTS, CSP, X-Frame | Partial | Add helmet middleware | + +**Quick wins:** +- `@nestjs/throttler` for rate limiting (30min) +- `helmet` middleware for security headers (15min) +- Audit log table + middleware (1-2h) + +--- + +## 7. Real-Time Updates Pattern + +All four dashboards use real-time updates differently: +- openclaw-dashboard: SSE (`/api/live`) +- clawd-control: SSE +- claw-dashboard: Polling (2s interval) +- ai-maestro: WebSocket + +**For Mosaic Stack:** Already has WebSocket for terminal. Use SSE for fleet status (simpler than WebSocket, one-directional is fine). Polling for non-critical pages. + +--- + +## Feature Comparison Matrix + +| Feature | openclaw-dash | clawd-control | claw-dash | ai-maestro | Mosaic Stack | +|---------|:---:|:---:|:---:|:---:|:---:| +| Session mgmt | ✅ | ✅ | ✅ | ✅ | ✅ | +| Memory viewer | ✅ | ❌ | ❌ | ✅ | ❌ | +| Cron mgmt | ✅ | ❌ | ❌ | ❌ | ❌ | +| Agent wizard | ❌ | ✅ | ❌ | ✅ | ❌ | +| Fleet overview | ✅ | ✅ | ❌ | ✅ | Partial | +| Multi-machine | ❌ | ❌ | ❌ | ✅ | ❌ | +| Agent messaging | ❌ | ❌ | ❌ | ✅ | ❌ | +| Rate limiting | ✅ | ✅ | ❌ | ❌ | ❌ | +| TOTP MFA | ✅ | ❌ | ❌ | ❌ | ❌ | +| Real-time | SSE | SSE | Poll | WS | WS (terminal) | +| Cost tracking | ✅ | ❌ | ❌ | ❌ | ✅ (usage) | +| Terminal UI | ❌ | ❌ | ✅ | ❌ | ✅ (xterm.js) | +| Kanban | ❌ | ❌ | ❌ | ✅ | ✅ | +| Auth | PBKDF2+MFA | Password | None | N/A | Better Auth | +| RBAC | ❌ | ❌ | ❌ | ❌ | ✅ |