mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 00:21:21 +00:00
- Add comprehensive "File Preview Tab System" section to ARCHITECTURE.md documenting features, interfaces, unified tab system, session fields, behavior, extension color mapping, and key files - Update CLAUDE-SESSION.md with FilePreviewTab interface and session fields - Add file preview tab modification entry to CLAUDE.md Key Files table
1664 lines
54 KiB
Markdown
1664 lines
54 KiB
Markdown
# Architecture Guide
|
||
|
||
Deep technical documentation for Maestro's architecture and design patterns. For quick reference, see [CLAUDE.md](CLAUDE.md). For development setup, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||
|
||
## Table of Contents
|
||
|
||
- [Dual-Process Architecture](#dual-process-architecture)
|
||
- [IPC Security Model](#ipc-security-model)
|
||
- [Process Manager](#process-manager)
|
||
- [Layer Stack System](#layer-stack-system)
|
||
- [Custom Hooks](#custom-hooks)
|
||
- [Services Layer](#services-layer)
|
||
- [Custom AI Commands](#custom-ai-commands)
|
||
- [Theme System](#theme-system)
|
||
- [Settings Persistence](#settings-persistence)
|
||
- [Claude Sessions API](#claude-sessions-api)
|
||
- [Auto Run System](#auto-run-system)
|
||
- [Achievement System](#achievement-system)
|
||
- [AI Tab System](#ai-tab-system)
|
||
- [File Preview Tab System](#file-preview-tab-system)
|
||
- [Execution Queue](#execution-queue)
|
||
- [Navigation History](#navigation-history)
|
||
- [Group Chat System](#group-chat-system)
|
||
- [Web/Mobile Interface](#webmobile-interface)
|
||
- [CLI Tool](#cli-tool)
|
||
- [Shared Module](#shared-module)
|
||
- [Remote Access & Tunnels](#remote-access--tunnels)
|
||
- [Error Handling Patterns](#error-handling-patterns)
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
Maestro organizes work into **Projects** (workspaces), each with a **CLI Terminal** and multiple **Agent Tabs**. Each tab can be connected to an **Agent Session** - either newly created or resumed from the session pool.
|
||
|
||
```mermaid
|
||
graph LR
|
||
subgraph Maestro["Maestro App"]
|
||
subgraph ProjectA["Agent A (workspace)"]
|
||
TermA[CLI Terminal]
|
||
subgraph TabsA["Agent Tabs"]
|
||
Tab1A[Tab 1]
|
||
Tab2A[Tab 2]
|
||
end
|
||
end
|
||
subgraph ProjectB["Agent B (workspace)"]
|
||
TermB[CLI Terminal]
|
||
subgraph TabsB["Agent Tabs"]
|
||
Tab1B[Tab 1]
|
||
Tab2B[Tab 2]
|
||
end
|
||
end
|
||
end
|
||
|
||
subgraph SessionPool["Agent Session Pool"]
|
||
direction TB
|
||
S1["Session α"]
|
||
S2["Session β"]
|
||
S3["Session γ"]
|
||
S4["Session δ"]
|
||
S5["..."]
|
||
end
|
||
|
||
Tab1A -.->|"resume"| S1
|
||
Tab2A -.->|"resume"| S2
|
||
Tab1B -.->|"resume"| S3
|
||
Tab2B -.->|"new"| S4
|
||
|
||
style Maestro fill:#9b8cd6,stroke:#6b5b95
|
||
style ProjectA fill:#87ceeb,stroke:#4682b4
|
||
style ProjectB fill:#87ceeb,stroke:#4682b4
|
||
style TermA fill:#90ee90,stroke:#228b22
|
||
style TermB fill:#90ee90,stroke:#228b22
|
||
style TabsA fill:#ffe4a0,stroke:#daa520
|
||
style TabsB fill:#ffe4a0,stroke:#daa520
|
||
style SessionPool fill:#ffb6c1,stroke:#dc143c
|
||
```
|
||
|
||
## Dual-Process Architecture
|
||
|
||
Maestro uses Electron's main/renderer split with strict context isolation.
|
||
|
||
### Main Process (`src/main/`)
|
||
|
||
Node.js backend with full system access:
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `index.ts` | App entry, IPC handlers, window management |
|
||
| `process-manager.ts` | PTY and child process spawning |
|
||
| `web-server.ts` | Fastify HTTP/WebSocket server for mobile remote control |
|
||
| `agent-detector.ts` | Auto-detect CLI tools via PATH |
|
||
| `preload.ts` | Secure IPC bridge via contextBridge |
|
||
| `tunnel-manager.ts` | Cloudflare tunnel management for secure remote access |
|
||
| `themes.ts` | Theme definitions for web interface (mirrors renderer themes) |
|
||
| `utils/execFile.ts` | Safe command execution utility |
|
||
| `utils/logger.ts` | System logging with levels |
|
||
| `utils/shellDetector.ts` | Detect available shells |
|
||
| `utils/terminalFilter.ts` | Strip terminal control sequences |
|
||
| `utils/cliDetection.ts` | CLI tool detection (cloudflared, gh) |
|
||
| `utils/networkUtils.ts` | Network utilities for local IP detection |
|
||
|
||
### Renderer Process (`src/renderer/`)
|
||
|
||
React frontend with no direct Node.js access:
|
||
|
||
| Directory | Purpose |
|
||
|-----------|---------|
|
||
| `components/` | React UI components |
|
||
| `hooks/` | Custom React hooks (15 hooks - see [Custom Hooks](#custom-hooks)) |
|
||
| `services/` | IPC wrappers (git.ts, process.ts) |
|
||
| `contexts/` | React contexts (LayerStackContext, ToastContext) |
|
||
| `constants/` | Themes, shortcuts, modal priorities |
|
||
| `types/` | TypeScript definitions |
|
||
| `utils/` | Frontend utilities |
|
||
|
||
### Session Model
|
||
|
||
Each session runs **two processes simultaneously**:
|
||
|
||
```typescript
|
||
interface Session {
|
||
id: string; // Unique identifier
|
||
aiPid: number; // AI agent process (suffixed -ai)
|
||
terminalPid: number; // Terminal process (suffixed -terminal)
|
||
inputMode: 'ai' | 'terminal'; // Which process receives input
|
||
// ... other fields
|
||
}
|
||
```
|
||
|
||
This enables seamless switching between AI and terminal modes without process restarts.
|
||
|
||
---
|
||
|
||
## IPC Security Model
|
||
|
||
All renderer-to-main communication uses the preload script:
|
||
|
||
- **Context isolation**: Enabled (renderer has no Node.js access)
|
||
- **Node integration**: Disabled (no `require()` in renderer)
|
||
- **Preload script**: Exposes minimal API via `contextBridge.exposeInMainWorld('maestro', ...)`
|
||
|
||
### The `window.maestro` API
|
||
|
||
```typescript
|
||
window.maestro = {
|
||
// Core persistence
|
||
settings: { get, set, getAll },
|
||
sessions: { getAll, setAll },
|
||
groups: { getAll, setAll },
|
||
history: { getAll, setAll }, // Command history persistence
|
||
|
||
// Process management
|
||
process: { spawn, write, interrupt, kill, resize, runCommand, onData, onExit, onSessionId, onStderr, onCommandExit, onUsage },
|
||
|
||
// Git operations (expanded)
|
||
git: {
|
||
status, diff, isRepo, numstat,
|
||
branches, tags, branch, log, show, showFile,
|
||
// Worktree operations
|
||
worktreeInfo, worktreeSetup, worktreeCheckout, getRepoRoot,
|
||
// PR creation
|
||
createPR, getDefaultBranch, checkGhCli
|
||
},
|
||
|
||
// File system
|
||
fs: { readDir, readFile },
|
||
|
||
// Agent management
|
||
agents: { detect, get, getConfig, setConfig, getConfigValue, setConfigValue },
|
||
|
||
// Claude Code integration
|
||
claude: { listSessions, readSessionMessages, searchSessions, getGlobalStats, onGlobalStatsUpdate },
|
||
|
||
// UI utilities
|
||
dialog: { selectFolder },
|
||
fonts: { detect },
|
||
shells: { detect },
|
||
shell: { openExternal },
|
||
devtools: { open, close, toggle },
|
||
|
||
// Logging
|
||
logger: { log, getLogs, clearLogs, setLogLevel, getLogLevel, setMaxLogBuffer, getMaxLogBuffer },
|
||
|
||
// Web/remote interface
|
||
webserver: { getUrl, getClientCount },
|
||
web: { broadcastUserInput, broadcastAutoRunState, broadcastTabChange },
|
||
live: { setSessionLive, getSessionLive },
|
||
tunnel: { start, stop, getStatus, onStatusChange },
|
||
|
||
// Auto Run
|
||
autorun: { listDocs, readDoc, writeDoc, saveImage, deleteImage, listImages },
|
||
playbooks: { list, create, update, delete },
|
||
|
||
// Attachments & temp files
|
||
attachments: { save, list, delete, clear },
|
||
tempfile: { write, read, delete },
|
||
|
||
// Activity & notifications
|
||
cli: { trackActivity, getActivity },
|
||
notification: { show, speak },
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Process Manager
|
||
|
||
The `ProcessManager` class (`src/main/process-manager.ts`) handles two process types:
|
||
|
||
### PTY Processes (via `node-pty`)
|
||
|
||
Used for terminal sessions with full shell emulation:
|
||
- `toolType: 'terminal'`
|
||
- Supports resize, ANSI escape codes, interactive shell
|
||
- Spawned with shell (zsh, bash, fish, etc.)
|
||
|
||
### Child Processes (via `child_process.spawn`)
|
||
|
||
Used for AI assistants:
|
||
- All non-terminal tool types
|
||
- Direct stdin/stdout/stderr capture
|
||
- **Security**: Uses `spawn()` with `shell: false`
|
||
|
||
### Batch Mode (Claude Code)
|
||
|
||
Claude Code runs in batch mode with `--print --output-format json`:
|
||
- Prompt passed as CLI argument
|
||
- Process exits after response
|
||
- JSON response parsed for result and usage stats
|
||
|
||
### Stream-JSON Mode (with images)
|
||
|
||
When images are attached:
|
||
- Uses `--input-format stream-json --output-format stream-json`
|
||
- Message sent via stdin as JSONL
|
||
- Supports multimodal input
|
||
|
||
### Process Events
|
||
|
||
```typescript
|
||
processManager.on('data', (sessionId, data) => { ... });
|
||
processManager.on('exit', (sessionId, code) => { ... });
|
||
processManager.on('usage', (sessionId, usageStats) => { ... });
|
||
processManager.on('session-id', (sessionId, agentSessionId) => { ... });
|
||
processManager.on('stderr', (sessionId, data) => { ... });
|
||
processManager.on('command-exit', (sessionId, code) => { ... });
|
||
```
|
||
|
||
---
|
||
|
||
## Layer Stack System
|
||
|
||
Centralized modal/overlay management with predictable Escape key handling.
|
||
|
||
### Problem Solved
|
||
|
||
- Previously had 9+ scattered Escape handlers
|
||
- Brittle modal detection with massive boolean checks
|
||
- Inconsistent focus management
|
||
|
||
### Architecture
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `hooks/useLayerStack.ts` | Core layer management hook |
|
||
| `contexts/LayerStackContext.tsx` | Global Escape handler (capture phase) |
|
||
| `constants/modalPriorities.ts` | Priority values for all modals |
|
||
| `types/layer.ts` | Layer type definitions |
|
||
|
||
### Modal Priority Hierarchy
|
||
|
||
```typescript
|
||
const MODAL_PRIORITIES = {
|
||
STANDING_OVATION: 1100, // Achievement celebration overlay
|
||
CONFIRM: 1000, // Highest - confirmation dialogs
|
||
PLAYBOOK_DELETE_CONFIRM: 950,
|
||
PLAYBOOK_NAME: 940,
|
||
RENAME_INSTANCE: 900,
|
||
RENAME_TAB: 880,
|
||
RENAME_GROUP: 850,
|
||
CREATE_GROUP: 800,
|
||
NEW_INSTANCE: 750,
|
||
AGENT_PROMPT_COMPOSER: 730,
|
||
PROMPT_COMPOSER: 710,
|
||
QUICK_ACTION: 700, // Command palette (Cmd+K)
|
||
TAB_SWITCHER: 690,
|
||
AGENT_SESSIONS: 680,
|
||
EXECUTION_QUEUE_BROWSER: 670,
|
||
BATCH_RUNNER: 660,
|
||
SHORTCUTS_HELP: 650,
|
||
HISTORY_HELP: 640,
|
||
AUTORUNNER_HELP: 630,
|
||
HISTORY_DETAIL: 620,
|
||
ABOUT: 600,
|
||
PROCESS_MONITOR: 550,
|
||
LOG_VIEWER: 500,
|
||
SETTINGS: 450,
|
||
GIT_DIFF: 200,
|
||
GIT_LOG: 190,
|
||
LIGHTBOX: 150,
|
||
FILE_PREVIEW: 100,
|
||
SLASH_AUTOCOMPLETE: 50,
|
||
TEMPLATE_AUTOCOMPLETE: 40,
|
||
FILE_TREE_FILTER: 30, // Lowest
|
||
};
|
||
```
|
||
|
||
### Registering a Modal
|
||
|
||
```typescript
|
||
import { useLayerStack } from '../contexts/LayerStackContext';
|
||
import { MODAL_PRIORITIES } from '../constants/modalPriorities';
|
||
|
||
const { registerLayer, unregisterLayer, updateLayerHandler } = useLayerStack();
|
||
const layerIdRef = useRef<string>();
|
||
|
||
// Use ref to avoid re-registration when callback identity changes
|
||
const onCloseRef = useRef(onClose);
|
||
onCloseRef.current = onClose;
|
||
|
||
useEffect(() => {
|
||
if (modalOpen) {
|
||
const id = registerLayer({
|
||
type: 'modal',
|
||
priority: MODAL_PRIORITIES.YOUR_MODAL,
|
||
blocksLowerLayers: true,
|
||
capturesFocus: true,
|
||
focusTrap: 'strict', // 'strict' | 'lenient' | 'none'
|
||
ariaLabel: 'Your Modal Name',
|
||
onEscape: () => onCloseRef.current(),
|
||
});
|
||
layerIdRef.current = id;
|
||
return () => unregisterLayer(id);
|
||
}
|
||
}, [modalOpen, registerLayer, unregisterLayer]); // onClose NOT in deps
|
||
```
|
||
|
||
### Layer Types
|
||
|
||
```typescript
|
||
type ModalLayer = {
|
||
type: 'modal';
|
||
priority: number;
|
||
blocksLowerLayers: boolean;
|
||
capturesFocus: boolean;
|
||
focusTrap: 'strict' | 'lenient' | 'none';
|
||
ariaLabel?: string;
|
||
onEscape: () => void;
|
||
onBeforeClose?: () => Promise<boolean>;
|
||
isDirty?: boolean;
|
||
parentModalId?: string;
|
||
};
|
||
|
||
type OverlayLayer = {
|
||
type: 'overlay';
|
||
priority: number;
|
||
blocksLowerLayers: boolean;
|
||
capturesFocus: boolean;
|
||
focusTrap: 'strict' | 'lenient' | 'none';
|
||
ariaLabel?: string;
|
||
onEscape: () => void;
|
||
allowClickOutside: boolean;
|
||
};
|
||
```
|
||
|
||
### Internal Search Layers
|
||
|
||
Components like FilePreview handle internal search in their onEscape:
|
||
|
||
```typescript
|
||
onEscape: () => {
|
||
if (searchOpen) {
|
||
setSearchOpen(false); // First Escape closes search
|
||
} else {
|
||
closePreview(); // Second Escape closes preview
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Custom Hooks
|
||
|
||
Maestro uses 15 custom hooks for state management and functionality.
|
||
|
||
### Core Hooks
|
||
|
||
#### useSettings (`src/renderer/hooks/useSettings.ts`)
|
||
|
||
Manages all application settings with automatic persistence.
|
||
|
||
**What it manages:**
|
||
- LLM settings (provider, model, API key)
|
||
- Agent settings (default agent, custom agent paths)
|
||
- Shell settings (default shell)
|
||
- Font settings (family, size, custom fonts)
|
||
- UI settings (theme, enter-to-send modes, panel widths, markdown mode)
|
||
- Terminal settings (width)
|
||
- Logging settings (level, buffer size)
|
||
- Output settings (max lines)
|
||
- Keyboard shortcuts
|
||
- Custom AI commands
|
||
|
||
#### useSessionManager (`src/renderer/hooks/useSessionManager.ts`)
|
||
|
||
Manages sessions and groups with CRUD operations.
|
||
|
||
**Key methods:**
|
||
- `createNewSession(agentId, workingDir, name)` - Creates new session with dual processes
|
||
- `deleteSession(id, showConfirmation)` - Delete with confirmation
|
||
- `toggleInputMode()` - Switch between AI and terminal mode
|
||
- `updateScratchPad(content)` - Update session scratchpad
|
||
- `createNewGroup(name, emoji, moveSession, activeSessionId)`
|
||
- Drag and drop handlers
|
||
|
||
#### useFileTreeManagement (`src/renderer/hooks/useFileTreeManagement.ts`)
|
||
|
||
Manages file tree refresh/filter state and git-related file metadata.
|
||
|
||
**Key methods:**
|
||
- `refreshFileTree(sessionId)` - Reload directory tree and return change stats
|
||
- `refreshGitFileState(sessionId)` - Refresh tree + git repo metadata
|
||
- `filteredFileTree` - Derived tree based on filter string
|
||
|
||
#### useBatchProcessor (`src/renderer/hooks/useBatchProcessor.ts`)
|
||
|
||
Manages Auto Run batch execution logic.
|
||
|
||
**Key methods:**
|
||
- `startBatchRun(config)` - Start batch document processing
|
||
- `stopBatchRun()` - Stop current batch run
|
||
- `pauseBatchRun()` / `resumeBatchRun()` - Pause/resume execution
|
||
|
||
### UI Management Hooks
|
||
|
||
#### useLayerStack (`src/renderer/hooks/useLayerStack.ts`)
|
||
|
||
Core layer management for modals and overlays.
|
||
|
||
**Key methods:**
|
||
- `registerLayer(config)` - Register a modal/overlay
|
||
- `unregisterLayer(id)` - Remove a layer
|
||
- `updateLayerHandler(id, handler)` - Update escape handler
|
||
|
||
#### useNavigationHistory (`src/renderer/hooks/useNavigationHistory.ts`)
|
||
|
||
Back/forward navigation through sessions and tabs. See [Navigation History](#navigation-history).
|
||
|
||
### Input & Autocomplete Hooks
|
||
|
||
#### useAtMentionCompletion (`src/renderer/hooks/useAtMentionCompletion.ts`)
|
||
|
||
Handles @-mention autocomplete for file references in prompts.
|
||
|
||
#### useTabCompletion (`src/renderer/hooks/useTabCompletion.ts`)
|
||
|
||
Tab completion utilities for terminal-style input.
|
||
|
||
#### useTemplateAutocomplete (`src/renderer/hooks/useTemplateAutocomplete.ts`)
|
||
|
||
Template variable autocomplete (e.g., `{{date}}`, `{{time}}`).
|
||
|
||
### Feature Hooks
|
||
|
||
#### useAchievements (`src/renderer/hooks/useAchievements.ts`)
|
||
|
||
Achievement/badge system for Auto Run usage. See [Achievement System](#achievement-system).
|
||
|
||
#### useActivityTracker (`src/renderer/hooks/useActivityTracker.ts`)
|
||
|
||
User activity tracking for session idle detection and status.
|
||
|
||
#### useMobileLandscape (`src/renderer/hooks/useMobileLandscape.ts`)
|
||
|
||
Mobile landscape orientation detection for responsive layouts.
|
||
|
||
---
|
||
|
||
## Services Layer
|
||
|
||
Services provide clean wrappers around IPC calls.
|
||
|
||
### Git Service (`src/renderer/services/git.ts`)
|
||
|
||
```typescript
|
||
import { gitService } from '../services/git';
|
||
|
||
const isRepo = await gitService.isRepo(cwd);
|
||
const status = await gitService.getStatus(cwd);
|
||
// Returns: { files: [{ path: string, status: string }] }
|
||
|
||
const diff = await gitService.getDiff(cwd, ['file1.ts']);
|
||
// Returns: { diff: string }
|
||
|
||
const numstat = await gitService.getNumstat(cwd);
|
||
// Returns: { files: [{ path, additions, deletions }] }
|
||
```
|
||
|
||
### Process Service (`src/renderer/services/process.ts`)
|
||
|
||
```typescript
|
||
import { processService } from '../services/process';
|
||
|
||
await processService.spawn(config);
|
||
await processService.write(sessionId, 'input\n');
|
||
await processService.interrupt(sessionId); // SIGINT/Ctrl+C
|
||
await processService.kill(sessionId);
|
||
await processService.resize(sessionId, cols, rows);
|
||
|
||
const unsubscribe = processService.onData((sessionId, data) => { ... });
|
||
```
|
||
|
||
---
|
||
|
||
## Custom AI Commands
|
||
|
||
User-defined prompt macros that expand when typed. The built-in slash commands (`/clear`, `/jump`, `/history`) have been deprecated in favor of fully customizable commands defined in Settings.
|
||
|
||
### Overview
|
||
|
||
Custom AI Commands are prompt templates that:
|
||
- Start with `/` prefix
|
||
- Expand to full prompts when selected
|
||
- Support template variables (e.g., `{{date}}`, `{{time}}`, `{{cwd}}`)
|
||
- Can be AI-only, terminal-only, or both modes
|
||
|
||
### Configuration
|
||
|
||
Commands are defined in Settings → Custom AI Commands:
|
||
|
||
```typescript
|
||
interface CustomAICommand {
|
||
command: string; // e.g., "/review"
|
||
description: string; // Shown in autocomplete
|
||
prompt: string; // The expanded prompt text
|
||
aiOnly?: boolean; // Only show in AI mode
|
||
terminalOnly?: boolean; // Only show in terminal mode
|
||
}
|
||
```
|
||
|
||
### Template Variables
|
||
|
||
Commands support these template variables:
|
||
- `{{date}}` - Current date (YYYY-MM-DD)
|
||
- `{{time}}` - Current time (HH:MM:SS)
|
||
- `{{datetime}}` - Combined date and time
|
||
- `{{cwd}}` - Current working directory
|
||
- `{{session}}` - Session name
|
||
- `{{agent}}` - Agent type (claude-code, etc.)
|
||
|
||
### Example Commands
|
||
|
||
```typescript
|
||
// Code review command
|
||
{
|
||
command: '/review',
|
||
description: 'Review staged changes',
|
||
prompt: 'Review the staged git changes and provide feedback on code quality, potential bugs, and improvements.',
|
||
aiOnly: true
|
||
}
|
||
|
||
// Status check
|
||
{
|
||
command: '/status',
|
||
description: 'Project status summary',
|
||
prompt: 'Give me a brief status of this project as of {{datetime}}. What files have been modified recently?',
|
||
aiOnly: true
|
||
}
|
||
```
|
||
|
||
### Claude Code CLI Commands
|
||
|
||
Maestro also fetches slash commands from Claude Code CLI when available, making Claude Code's built-in commands accessible through the same autocomplete interface.
|
||
|
||
---
|
||
|
||
## Theme System
|
||
|
||
Themes defined in `src/renderer/constants/themes.ts`.
|
||
|
||
### Theme Structure
|
||
|
||
```typescript
|
||
interface Theme {
|
||
id: ThemeId;
|
||
name: string;
|
||
mode: 'light' | 'dark' | 'vibe';
|
||
colors: {
|
||
bgMain: string; // Main content background
|
||
bgSidebar: string; // Sidebar background
|
||
bgActivity: string; // Accent background
|
||
border: string; // Border colors
|
||
textMain: string; // Primary text
|
||
textDim: string; // Secondary text
|
||
accent: string; // Accent color
|
||
accentDim: string; // Dimmed accent
|
||
accentText: string; // Accent text color
|
||
accentForeground: string; // Text ON accent backgrounds (contrast)
|
||
success: string; // Success state (green)
|
||
warning: string; // Warning state (yellow)
|
||
error: string; // Error state (red)
|
||
};
|
||
}
|
||
```
|
||
|
||
### Available Themes
|
||
|
||
**Dark themes:** Dracula, Monokai, Nord, Tokyo Night, Catppuccin Mocha, Gruvbox Dark
|
||
|
||
**Light themes:** GitHub, Solarized, One Light, Gruvbox Light, Catppuccin Latte, Ayu Light
|
||
|
||
### Usage
|
||
|
||
Use inline styles for theme colors:
|
||
```typescript
|
||
style={{ color: theme.colors.textMain }} // Correct
|
||
```
|
||
|
||
Use Tailwind for layout:
|
||
```typescript
|
||
className="flex items-center gap-2" // Correct
|
||
```
|
||
|
||
---
|
||
|
||
## Settings Persistence
|
||
|
||
Settings stored via `electron-store`:
|
||
|
||
**Locations:**
|
||
- **macOS**: `~/Library/Application Support/maestro/`
|
||
- **Windows**: `%APPDATA%/maestro/`
|
||
- **Linux**: `~/.config/maestro/`
|
||
|
||
**Files:**
|
||
- `maestro-settings.json` - User preferences
|
||
- `maestro-sessions.json` - Session persistence
|
||
- `maestro-groups.json` - Session groups
|
||
- `maestro-agent-configs.json` - Per-agent configuration
|
||
|
||
### Adding New Settings
|
||
|
||
1. Add state in `useSettings.ts`:
|
||
```typescript
|
||
const [mySetting, setMySettingState] = useState<MyType>(defaultValue);
|
||
```
|
||
|
||
2. Create wrapper function:
|
||
```typescript
|
||
const setMySetting = (value: MyType) => {
|
||
setMySettingState(value);
|
||
window.maestro.settings.set('mySetting', value);
|
||
};
|
||
```
|
||
|
||
3. Load in useEffect:
|
||
```typescript
|
||
const saved = await window.maestro.settings.get('mySetting');
|
||
if (saved !== undefined) setMySettingState(saved);
|
||
```
|
||
|
||
4. Add to return object and export.
|
||
|
||
---
|
||
|
||
## Claude Sessions API
|
||
|
||
Browse and resume Claude Code sessions from `~/.claude/projects/`.
|
||
|
||
### Path Encoding
|
||
|
||
Claude Code encodes project paths by replacing `/` with `-`:
|
||
- `/Users/pedram/Projects/Maestro` → `-Users-pedram-Projects-Maestro`
|
||
|
||
### IPC Handlers
|
||
|
||
```typescript
|
||
// List sessions for a project
|
||
const sessions = await window.maestro.claude.listSessions(projectPath);
|
||
// Returns: [{ sessionId, projectPath, timestamp, modifiedAt, firstMessage, messageCount, sizeBytes }]
|
||
|
||
// Read messages with pagination
|
||
const { messages, total, hasMore } = await window.maestro.claude.readSessionMessages(
|
||
projectPath,
|
||
sessionId,
|
||
{ offset: 0, limit: 20 }
|
||
);
|
||
|
||
// Search sessions
|
||
const results = await window.maestro.claude.searchSessions(
|
||
projectPath,
|
||
'query',
|
||
'all' // 'title' | 'user' | 'assistant' | 'all'
|
||
);
|
||
|
||
// Get global stats across all Claude projects (with streaming updates)
|
||
const stats = await window.maestro.claude.getGlobalStats();
|
||
// Returns: { totalSessions, totalMessages, totalInputTokens, totalOutputTokens,
|
||
// totalCacheReadTokens, totalCacheCreationTokens, totalCostUsd, totalSizeBytes }
|
||
|
||
// Subscribe to streaming updates during stats calculation
|
||
const unsubscribe = window.maestro.claude.onGlobalStatsUpdate((stats) => {
|
||
console.log(`Progress: ${stats.totalSessions} sessions, $${stats.totalCostUsd.toFixed(2)}`);
|
||
if (stats.isComplete) console.log('Stats calculation complete');
|
||
});
|
||
// Call unsubscribe() to stop listening
|
||
```
|
||
|
||
### UI Access
|
||
|
||
- Shortcut: `Cmd+Shift+L`
|
||
- Quick Actions: `Cmd+K` → "View Agent Sessions"
|
||
- Button in main panel header
|
||
|
||
---
|
||
|
||
## Auto Run System
|
||
|
||
File-based document runner for automating multi-step tasks. Users configure a folder of markdown documents containing checkbox tasks that are processed sequentially by AI agents.
|
||
|
||
### Component Architecture
|
||
|
||
| Component | Purpose |
|
||
|-----------|---------|
|
||
| `AutoRun.tsx` | Main panel showing current document with edit/preview modes |
|
||
| `AutoRunSetupModal.tsx` | First-time setup for selecting the Runner Docs folder |
|
||
| `AutoRunDocumentSelector.tsx` | Dropdown for switching between markdown documents |
|
||
| `BatchRunnerModal.tsx` | Configuration modal for running multiple Auto Run documents |
|
||
| `PlaybookNameModal.tsx` | Modal for naming saved playbook configurations |
|
||
| `PlaybookDeleteConfirmModal.tsx` | Confirmation modal for playbook deletion |
|
||
| `useBatchProcessor.ts` | Hook managing batch execution logic |
|
||
|
||
### Data Types
|
||
|
||
```typescript
|
||
// Document entry in the batch run queue (supports duplicates)
|
||
interface BatchDocumentEntry {
|
||
id: string; // Unique ID for drag-drop and duplicates
|
||
filename: string; // Document filename (without .md)
|
||
resetOnCompletion: boolean; // Uncheck all boxes when done
|
||
isDuplicate: boolean; // True if this is a duplicate entry
|
||
}
|
||
|
||
// Git worktree configuration for parallel work
|
||
interface WorktreeConfig {
|
||
enabled: boolean; // Whether to use a worktree
|
||
path: string; // Absolute path for the worktree
|
||
branchName: string; // Branch name to use/create
|
||
createPROnCompletion: boolean; // Create PR when Auto Run finishes
|
||
}
|
||
|
||
// Configuration for starting a batch run
|
||
interface BatchRunConfig {
|
||
documents: BatchDocumentEntry[]; // Ordered list of docs to run
|
||
prompt: string; // Agent prompt template
|
||
loopEnabled: boolean; // Loop back to first doc when done
|
||
worktree?: WorktreeConfig; // Optional worktree configuration
|
||
}
|
||
|
||
// Runtime batch processing state
|
||
interface BatchRunState {
|
||
isRunning: boolean;
|
||
isStopping: boolean;
|
||
documents: string[]; // Document filenames in order
|
||
currentDocumentIndex: number; // Which document we're on (0-based)
|
||
currentDocTasksTotal: number;
|
||
currentDocTasksCompleted: number;
|
||
totalTasksAcrossAllDocs: number;
|
||
completedTasksAcrossAllDocs: number;
|
||
loopEnabled: boolean;
|
||
loopIteration: number; // How many times we've looped
|
||
folderPath: string;
|
||
worktreeActive: boolean;
|
||
worktreePath?: string;
|
||
worktreeBranch?: string;
|
||
}
|
||
|
||
// Saved playbook configuration
|
||
interface Playbook {
|
||
id: string;
|
||
name: string;
|
||
createdAt: number;
|
||
updatedAt: number;
|
||
documents: PlaybookDocumentEntry[];
|
||
loopEnabled: boolean;
|
||
prompt: string;
|
||
worktreeSettings?: {
|
||
branchNameTemplate: string;
|
||
createPROnCompletion: boolean;
|
||
};
|
||
}
|
||
```
|
||
|
||
### Session Fields
|
||
|
||
Auto Run state is stored per-session:
|
||
|
||
```typescript
|
||
// In Session interface
|
||
autoRunFolderPath?: string; // Persisted folder path for Runner Docs
|
||
autoRunSelectedFile?: string; // Currently selected markdown filename
|
||
autoRunMode?: 'edit' | 'preview'; // Current editing mode
|
||
autoRunEditScrollPos?: number; // Scroll position in edit mode
|
||
autoRunPreviewScrollPos?: number; // Scroll position in preview mode
|
||
autoRunCursorPosition?: number; // Cursor position in edit mode
|
||
batchRunnerPrompt?: string; // Custom batch runner prompt
|
||
batchRunnerPromptModifiedAt?: number;
|
||
```
|
||
|
||
### IPC Handlers
|
||
|
||
```typescript
|
||
// List markdown files in a directory
|
||
'autorun:listDocs': (folderPath: string) => Promise<{ success, files, error? }>
|
||
|
||
// Read a markdown document
|
||
'autorun:readDoc': (folderPath: string, filename: string) => Promise<{ success, content, error? }>
|
||
|
||
// Write a markdown document
|
||
'autorun:writeDoc': (folderPath: string, filename: string, content: string) => Promise<{ success, error? }>
|
||
|
||
// Save image to folder
|
||
'autorun:saveImage': (folderPath: string, docName: string, base64Data: string, extension: string) =>
|
||
Promise<{ success, relativePath, error? }>
|
||
|
||
// Delete image
|
||
'autorun:deleteImage': (folderPath: string, relativePath: string) => Promise<{ success, error? }>
|
||
|
||
// List images for a document
|
||
'autorun:listImages': (folderPath: string, docName: string) => Promise<{ success, images, error? }>
|
||
|
||
// Playbook CRUD operations
|
||
'playbooks:list': (sessionId: string) => Promise<{ success, playbooks, error? }>
|
||
'playbooks:create': (sessionId: string, playbook) => Promise<{ success, playbook, error? }>
|
||
'playbooks:update': (sessionId: string, playbookId: string, updates) => Promise<{ success, playbook, error? }>
|
||
'playbooks:delete': (sessionId: string, playbookId: string) => Promise<{ success, error? }>
|
||
```
|
||
|
||
### Git Worktree Integration
|
||
|
||
When worktree is enabled, Auto Run operates in an isolated directory:
|
||
|
||
```typescript
|
||
// Check if worktree exists and get branch info
|
||
'git:worktreeInfo': (worktreePath: string) => Promise<{
|
||
success: boolean;
|
||
exists: boolean;
|
||
isWorktree: boolean;
|
||
currentBranch?: string;
|
||
repoRoot?: string;
|
||
}>
|
||
|
||
// Create or reuse a worktree
|
||
'git:worktreeSetup': (mainRepoCwd: string, worktreePath: string, branchName: string) => Promise<{
|
||
success: boolean;
|
||
created: boolean;
|
||
currentBranch: string;
|
||
branchMismatch: boolean;
|
||
}>
|
||
|
||
// Checkout a branch in a worktree
|
||
'git:worktreeCheckout': (worktreePath: string, branchName: string, createIfMissing: boolean) => Promise<{
|
||
success: boolean;
|
||
hasUncommittedChanges: boolean;
|
||
}>
|
||
|
||
// Create PR from worktree branch
|
||
'git:createPR': (worktreePath: string, baseBranch: string, title: string, body: string) => Promise<{
|
||
success: boolean;
|
||
prUrl?: string;
|
||
}>
|
||
```
|
||
|
||
### Execution Flow
|
||
|
||
1. **Setup**: User selects Runner Docs folder via `AutoRunSetupModal`
|
||
2. **Document Selection**: Documents appear in `AutoRunDocumentSelector` dropdown
|
||
3. **Editing**: `AutoRun` component provides edit/preview modes with auto-save (5s debounce)
|
||
4. **Batch Configuration**: `BatchRunnerModal` allows ordering documents, enabling loop/reset, configuring worktree
|
||
5. **Playbooks**: Save/load configurations for repeated batch runs
|
||
6. **Execution**: `useBatchProcessor` hook processes documents sequentially
|
||
7. **Progress**: RightPanel shows document and task-level progress
|
||
|
||
### Write Queue Integration
|
||
|
||
Without worktree mode, Auto Run tasks queue through the existing execution queue:
|
||
- Auto Run tasks are marked as write operations (`readOnlyMode: false`)
|
||
- Manual write messages queue behind Auto Run (sequential)
|
||
- Read-only operations from other tabs can run in parallel
|
||
|
||
With worktree mode:
|
||
- Auto Run operates in a separate directory
|
||
- No queue conflicts with main workspace
|
||
- True parallelization enabled
|
||
|
||
---
|
||
|
||
## Achievement System
|
||
|
||
Gamification system that rewards Auto Run usage with conductor-themed badges.
|
||
|
||
### Components
|
||
|
||
| Component | Purpose |
|
||
|-----------|---------|
|
||
| `conductorBadges.ts` | Badge definitions and progression levels |
|
||
| `useAchievements.ts` | Achievement tracking and unlock logic |
|
||
| `AchievementCard.tsx` | Individual badge display component |
|
||
| `StandingOvationOverlay.tsx` | Celebration overlay with confetti animations |
|
||
|
||
### Badge Progression
|
||
|
||
15 conductor levels based on cumulative Auto Run time:
|
||
|
||
| Level | Badge | Time Required |
|
||
|-------|-------|---------------|
|
||
| 1 | Apprentice Conductor | 1 minute |
|
||
| 2 | Junior Conductor | 5 minutes |
|
||
| 3 | Assistant Conductor | 15 minutes |
|
||
| 4 | Associate Conductor | 30 minutes |
|
||
| 5 | Conductor | 1 hour |
|
||
| 6 | Senior Conductor | 2 hours |
|
||
| 7 | Principal Conductor | 4 hours |
|
||
| 8 | Master Conductor | 8 hours |
|
||
| 9 | Chief Conductor | 16 hours |
|
||
| 10 | Distinguished Conductor | 24 hours |
|
||
| 11 | Elite Conductor | 48 hours |
|
||
| 12 | Virtuoso Conductor | 72 hours |
|
||
| 13 | Legendary Conductor | 100 hours |
|
||
| 14 | Mythic Conductor | 150 hours |
|
||
| 15 | Transcendent Maestro | 200 hours |
|
||
|
||
### Standing Ovation
|
||
|
||
When a new badge is unlocked:
|
||
1. `StandingOvationOverlay` displays with confetti animation
|
||
2. Badge details shown with celebration message
|
||
3. Share functionality available
|
||
4. Acknowledgment persisted to prevent re-showing
|
||
|
||
---
|
||
|
||
## AI Tab System
|
||
|
||
Multi-tab support within each session, allowing parallel conversations with separate Claude sessions.
|
||
|
||
### Features
|
||
|
||
- **Multiple tabs per session**: Each tab maintains its own Claude session ID
|
||
- **Tab management**: Create, close, rename, star tabs
|
||
- **Read-only mode**: Per-tab toggle to prevent accidental input
|
||
- **Save-to-history toggle**: Per-tab control over history persistence
|
||
- **Tab switcher modal**: Quick navigation via `Alt+Cmd+T`
|
||
- **Unread filtering**: Filter to show only tabs with unread messages
|
||
|
||
### Tab Interface
|
||
|
||
```typescript
|
||
interface AITab {
|
||
id: string;
|
||
name: string;
|
||
agentSessionId?: string; // Separate Claude session per tab
|
||
aiLogs: LogEntry[]; // Tab-specific conversation history
|
||
isStarred: boolean;
|
||
readOnlyMode: boolean;
|
||
saveToHistory: boolean;
|
||
unreadCount: number;
|
||
createdAt: number;
|
||
}
|
||
```
|
||
|
||
### Session Fields
|
||
|
||
```typescript
|
||
// In Session interface
|
||
aiTabs: AITab[]; // Array of AI tabs
|
||
activeAITabId: string; // Currently active tab ID
|
||
```
|
||
|
||
### Shortcuts
|
||
|
||
| Shortcut | Action |
|
||
|----------|--------|
|
||
| `Cmd+T` | New tab |
|
||
| `Cmd+W` | Close current tab |
|
||
| `Alt+Cmd+T` | Open tab switcher |
|
||
| `Cmd+Shift+]` | Next tab |
|
||
| `Cmd+Shift+[` | Previous tab |
|
||
|
||
---
|
||
|
||
## File Preview Tab System
|
||
|
||
In-tab file viewing that integrates file previews alongside AI conversation tabs. Files open as separate tabs within the tab bar, maintaining their own state and scroll positions.
|
||
|
||
### Features
|
||
|
||
- **Unified tab bar**: File tabs appear alongside AI tabs with consistent styling
|
||
- **Deduplication**: Same file opened twice within a session activates existing tab
|
||
- **Multi-session support**: Same file can be open in multiple sessions simultaneously
|
||
- **State persistence**: Scroll position, search query, and edit mode preserved across restarts
|
||
- **SSH remote files**: Supports viewing files from remote SSH hosts with loading states
|
||
- **Extension badges**: Color-coded file extension badges with theme-aware and colorblind-safe palettes
|
||
- **Overlay menu**: Right-click or hover menu for file operations (copy path, reveal in finder, etc.)
|
||
|
||
### File Tab Interface
|
||
|
||
```typescript
|
||
interface FilePreviewTab {
|
||
id: string; // Unique tab ID (UUID)
|
||
path: string; // Full file path
|
||
name: string; // Filename without extension (tab display name)
|
||
extension: string; // File extension with dot (e.g., '.md', '.ts')
|
||
content: string; // File content (loaded on open)
|
||
scrollTop: number; // Preserved scroll position
|
||
searchQuery: string; // Preserved search query
|
||
editMode: boolean; // Whether tab was in edit mode
|
||
editContent?: string; // Unsaved edit content (if pending changes)
|
||
createdAt: number; // Timestamp for ordering
|
||
lastModified: number; // File modification time (for refresh detection)
|
||
sshRemoteId?: string; // SSH remote ID for remote files
|
||
isLoading?: boolean; // True while content is being fetched
|
||
}
|
||
```
|
||
|
||
### Unified Tab System
|
||
|
||
AI and file tabs share a unified tab order managed by `unifiedTabOrder`:
|
||
|
||
```typescript
|
||
// Reference to any tab type
|
||
type UnifiedTabRef = { type: 'ai' | 'file'; id: string };
|
||
|
||
// Discriminated union for rendering
|
||
type UnifiedTab =
|
||
| { type: 'ai'; id: string; data: AITab }
|
||
| { type: 'file'; id: string; data: FilePreviewTab };
|
||
```
|
||
|
||
### Session Fields
|
||
|
||
```typescript
|
||
// In Session interface
|
||
filePreviewTabs: FilePreviewTab[]; // Array of file preview tabs
|
||
activeFileTabId: string | null; // Active file tab (null if AI tab active)
|
||
unifiedTabOrder: UnifiedTabRef[]; // Visual order of all tabs
|
||
closedUnifiedTabHistory: ClosedUnifiedTab[]; // Undo stack for Cmd+Shift+T
|
||
```
|
||
|
||
### Behavior
|
||
|
||
- **Opening files**: Double-click in file explorer, click file links in AI output, or use Go to File (`Cmd+P`)
|
||
- **Tab switching**: Click tab or use `Cmd+Shift+[`/`]` to navigate
|
||
- **Tab closing**: Click X button, use `Cmd+W`, or right-click → Close
|
||
- **Restore closed**: `Cmd+Shift+T` restores recently closed tabs (both AI and file)
|
||
- **Edit mode**: Toggle edit mode in file preview; unsaved changes prompt on close
|
||
|
||
### Extension Color Mapping
|
||
|
||
File tabs display a colored badge based on file extension. Colors are theme-aware (light/dark) and support colorblind-safe palettes:
|
||
|
||
| Extensions | Light Theme | Dark Theme | Colorblind (Wong palette) |
|
||
|------------|-------------|------------|---------------------------|
|
||
| .ts, .tsx, .js, .jsx | Blue | Light Blue | #0077BB |
|
||
| .md, .mdx, .txt | Green | Light Green | #009988 |
|
||
| .json, .yaml, .toml | Amber | Yellow | #EE7733 |
|
||
| .css, .scss, .less | Purple | Light Purple | #AA4499 |
|
||
| .html, .xml, .svg | Orange | Light Orange | #CC3311 |
|
||
| .py | Teal | Cyan | #33BBEE |
|
||
| .rs | Rust | Light Rust | #EE3377 |
|
||
| .go | Cyan | Light Cyan | #44AA99 |
|
||
| .sh, .bash, .zsh | Gray | Light Gray | #BBBBBB |
|
||
|
||
### Key Files
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `TabBar.tsx` | Unified tab rendering with AI and file tabs |
|
||
| `FilePreview.tsx` | File content viewer with edit mode |
|
||
| `MainPanel.tsx` | Coordinates tab display and file loading |
|
||
| `App.tsx` | File tab creation (`handleOpenFileTab`) |
|
||
| `useDebouncedPersistence.ts` | Persists file tabs across sessions |
|
||
|
||
---
|
||
|
||
## Execution Queue
|
||
|
||
Sequential message processing system that prevents race conditions when multiple operations target the same session.
|
||
|
||
### Components
|
||
|
||
| Component | Purpose |
|
||
|-----------|---------|
|
||
| `ExecutionQueueIndicator.tsx` | Shows queue status in tab bar |
|
||
| `ExecutionQueueBrowser.tsx` | Modal for viewing/managing queue |
|
||
|
||
### Queue Item Types
|
||
|
||
```typescript
|
||
interface QueuedItem {
|
||
id: string;
|
||
type: 'message' | 'command';
|
||
content: string;
|
||
tabId: string;
|
||
readOnlyMode: boolean;
|
||
timestamp: number;
|
||
source: 'user' | 'autorun';
|
||
}
|
||
```
|
||
|
||
### Behavior
|
||
|
||
- **Write operations** (readOnlyMode: false) queue sequentially
|
||
- **Read-only operations** can run in parallel
|
||
- Auto Run tasks queue with regular messages
|
||
- Queue visible via indicator in tab bar
|
||
- Users can cancel pending items via queue browser
|
||
|
||
### Session Fields
|
||
|
||
```typescript
|
||
// In Session interface
|
||
executionQueue: QueuedItem[]; // Pending operations
|
||
isProcessingQueue: boolean; // Currently processing
|
||
```
|
||
|
||
---
|
||
|
||
## Navigation History
|
||
|
||
Back/forward navigation through sessions and tabs, similar to browser history.
|
||
|
||
### Implementation
|
||
|
||
`useNavigationHistory` hook maintains a stack of navigation entries:
|
||
|
||
```typescript
|
||
interface NavigationEntry {
|
||
sessionId: string;
|
||
tabId?: string;
|
||
timestamp: number;
|
||
}
|
||
```
|
||
|
||
### Behavior
|
||
|
||
- Maximum 50 entries in history
|
||
- Automatic cleanup of invalid entries (deleted sessions/tabs)
|
||
- Skips duplicate consecutive entries
|
||
|
||
### Shortcuts
|
||
|
||
| Shortcut | Action |
|
||
|----------|--------|
|
||
| `Cmd+Shift+,` | Navigate back |
|
||
| `Cmd+Shift+.` | Navigate forward |
|
||
|
||
---
|
||
|
||
## Group Chat System
|
||
|
||
Multi-agent coordination system where a moderator AI orchestrates conversations between multiple Maestro agents, synthesizing their responses into cohesive answers.
|
||
|
||
### Architecture Overview
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ GROUP CHAT FLOW │
|
||
├─────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ User sends message │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────┐ │
|
||
│ │ MODERATOR │ ◄─── Receives user message + chat history │
|
||
│ └──────┬──────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ Has @mentions? ───No───► Return directly to user │
|
||
│ │ │
|
||
│ Yes │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌──────────────────────────────────────┐ │
|
||
│ │ Route to mentioned agents │ │
|
||
│ │ @AgentA @AgentB @AgentC ... │ │
|
||
│ └──────────────────────────────────────┘ │
|
||
│ │ │ │ │
|
||
│ ▼ ▼ ▼ │
|
||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||
│ │ Agent A │ │ Agent B │ │ Agent C │ (work in parallel) │
|
||
│ └────┬────┘ └────┬────┘ └────┬────┘ │
|
||
│ │ │ │ │
|
||
│ └───────────┴───────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ All agents responded │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────┐ │
|
||
│ │ MODERATOR reviews results │ ◄─── Synthesis round │
|
||
│ └──────────────┬──────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ Need more info? ───Yes───► @mention agents again (loop back) │
|
||
│ │ │
|
||
│ No │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌─────────────────────────────┐ │
|
||
│ │ Final synthesis to user │ ◄─── No @mentions = conversation ends │
|
||
│ └─────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Key Design Principle: Moderator Controls the Flow
|
||
|
||
The moderator is the traffic controller. After agents respond, the **moderator decides** what happens next:
|
||
|
||
1. **Continue with agents** - If responses are incomplete or unclear, @mention agents for follow-up
|
||
2. **Return to user** - If the question is fully answered, provide a synthesis WITHOUT any @mentions
|
||
|
||
This allows the moderator to go back and forth with agents as many times as needed until the user's question is properly answered.
|
||
|
||
### Component Architecture
|
||
|
||
```
|
||
src/main/group-chat/
|
||
├── group-chat-storage.ts # Persistence (JSON files in app data)
|
||
├── group-chat-log.ts # Chat history logging
|
||
├── group-chat-moderator.ts # Moderator process management & prompts
|
||
├── group-chat-router.ts # Message routing logic
|
||
└── group-chat-agent.ts # Participant agent management
|
||
```
|
||
|
||
| Component | Purpose |
|
||
|-----------|---------|
|
||
| `group-chat-storage.ts` | CRUD operations for group chats, participants, history entries |
|
||
| `group-chat-log.ts` | Append-only log of all messages (user, moderator, agents) |
|
||
| `group-chat-moderator.ts` | Spawns moderator AI, defines system prompts |
|
||
| `group-chat-router.ts` | Routes messages between user, moderator, and agents |
|
||
| `group-chat-agent.ts` | Adds/removes participant agents |
|
||
|
||
### Message Routing
|
||
|
||
#### Session ID Patterns
|
||
|
||
The router uses session ID patterns to identify message sources:
|
||
|
||
| Pattern | Source |
|
||
|---------|--------|
|
||
| `group-chat-{chatId}-moderator-{timestamp}` | Moderator process |
|
||
| `group-chat-{chatId}-participant-{name}-{timestamp}` | Agent participant |
|
||
|
||
#### Routing Functions
|
||
|
||
```typescript
|
||
// User → Moderator
|
||
routeUserMessage(groupChatId, message, processManager, agentDetector, readOnly)
|
||
|
||
// Moderator → Agents (extracts @mentions, spawns agent processes)
|
||
routeModeratorResponse(groupChatId, message, processManager, agentDetector, readOnly)
|
||
|
||
// Agent → Back to moderator (triggers synthesis when all agents respond)
|
||
routeAgentResponse(groupChatId, participantName, message, processManager)
|
||
|
||
// Synthesis round (after all agents respond)
|
||
spawnModeratorSynthesis(groupChatId, processManager, agentDetector)
|
||
```
|
||
|
||
### The Synthesis Flow
|
||
|
||
When all agents finish responding:
|
||
|
||
1. `markParticipantResponded()` tracks pending responses
|
||
2. When last agent responds, `spawnModeratorSynthesis()` is called
|
||
3. Moderator receives all agent responses in chat history
|
||
4. Moderator either:
|
||
- **@mentions agents** → Routes back to agents (loop continues)
|
||
- **No @mentions** → Final response to user (loop ends)
|
||
|
||
This is enforced by `routeModeratorResponse()` which checks for @mentions:
|
||
|
||
```typescript
|
||
// Extract mentions and route to agents
|
||
const mentions = extractMentions(message, participants);
|
||
if (mentions.length > 0) {
|
||
// Spawn agent processes, track pending responses
|
||
for (const participantName of mentions) {
|
||
// ... spawn batch process for agent
|
||
participantsToRespond.add(participantName);
|
||
}
|
||
pendingParticipantResponses.set(groupChatId, participantsToRespond);
|
||
}
|
||
// If no mentions, message is logged but no agents are spawned
|
||
// = conversation turn complete, ball is back with user
|
||
```
|
||
|
||
### Moderator Prompts
|
||
|
||
Two key prompts control moderator behavior:
|
||
|
||
**MODERATOR_SYSTEM_PROMPT** - Base instructions for all moderator interactions:
|
||
- Assist user directly for simple questions
|
||
- Coordinate agents via @mentions when needed
|
||
- Control conversation flow
|
||
- Return to user only when answer is complete
|
||
|
||
**MODERATOR_SYNTHESIS_PROMPT** - Used when reviewing agent responses:
|
||
- Synthesize if responses are complete (NO @mentions)
|
||
- @mention agents if more info needed
|
||
- Keep going until user's question is answered
|
||
|
||
### Data Flow Example
|
||
|
||
```
|
||
User: "How does @Maestro and @RunMaestro.ai relate?"
|
||
|
||
1. routeUserMessage()
|
||
- Logs message as "user"
|
||
- Auto-adds @Maestro and @RunMaestro.ai as participants
|
||
- Spawns moderator process with user message
|
||
|
||
2. Moderator responds: "Let me ask the agents. @Maestro @RunMaestro.ai explain..."
|
||
routeModeratorResponse()
|
||
- Logs message as "moderator"
|
||
- Extracts mentions: ["Maestro", "RunMaestro.ai"]
|
||
- Spawns batch processes for each agent
|
||
- Sets pendingParticipantResponses = {"Maestro", "RunMaestro.ai"}
|
||
|
||
3. Agent "Maestro" responds
|
||
routeAgentResponse()
|
||
- Logs message as "Maestro"
|
||
- markParticipantResponded() → pendingParticipantResponses = {"RunMaestro.ai"}
|
||
- Not last agent, so no synthesis yet
|
||
|
||
4. Agent "RunMaestro.ai" responds
|
||
routeAgentResponse()
|
||
- Logs message as "RunMaestro.ai"
|
||
- markParticipantResponded() → pendingParticipantResponses = {} (empty)
|
||
- Last agent! Triggers spawnModeratorSynthesis()
|
||
|
||
5. Moderator synthesis
|
||
- Receives all agent responses in chat history
|
||
- Decision point:
|
||
a) If needs more info: "@Maestro can you clarify..." → back to step 2
|
||
b) If satisfied: "Here's how they relate..." (no @mentions) → done
|
||
|
||
6. Final response to user (no @mentions = turn complete)
|
||
```
|
||
|
||
### IPC Handlers
|
||
|
||
```typescript
|
||
// Group chat management
|
||
'groupchat:create': (name, moderatorAgentId) => Promise<GroupChat>
|
||
'groupchat:get': (id) => Promise<GroupChat | null>
|
||
'groupchat:list': () => Promise<GroupChat[]>
|
||
'groupchat:delete': (id) => Promise<void>
|
||
|
||
// Participant management
|
||
'groupchat:addParticipant': (chatId, name, agentId, cwd) => Promise<void>
|
||
'groupchat:removeParticipant': (chatId, name) => Promise<void>
|
||
|
||
// Messaging
|
||
'groupchat:sendMessage': (chatId, message, readOnly?) => Promise<void>
|
||
|
||
// State
|
||
'groupchat:getMessages': (chatId, limit?) => Promise<GroupChatMessage[]>
|
||
'groupchat:getHistory': (chatId) => Promise<GroupChatHistoryEntry[]>
|
||
```
|
||
|
||
### Events
|
||
|
||
```typescript
|
||
// Real-time updates to renderer
|
||
groupChatEmitters.emitMessage(chatId, message) // New message
|
||
groupChatEmitters.emitStateChange(chatId, state) // 'idle' | 'agent-working'
|
||
groupChatEmitters.emitParticipantsChanged(chatId, list) // Participant list updated
|
||
groupChatEmitters.emitHistoryEntry(chatId, entry) // New history entry
|
||
groupChatEmitters.emitModeratorUsage(chatId, usage) // Token usage stats
|
||
```
|
||
|
||
### Storage Structure
|
||
|
||
```
|
||
~/Library/Application Support/maestro/group-chats/
|
||
├── {chatId}/
|
||
│ ├── chat.json # Group chat metadata
|
||
│ ├── log.jsonl # Append-only message log
|
||
│ └── history.json # Summarized history entries
|
||
```
|
||
|
||
---
|
||
|
||
## Web/Mobile Interface
|
||
|
||
Progressive Web App (PWA) for remote control of Maestro from mobile devices.
|
||
|
||
### Architecture
|
||
|
||
```
|
||
src/web/
|
||
├── index.ts # Entry point
|
||
├── main.tsx # React entry
|
||
├── index.html # HTML template
|
||
├── index.css # Global styles
|
||
├── components/ # Shared components (Button, Card, Input, Badge)
|
||
├── hooks/ # Web-specific hooks
|
||
├── mobile/ # Mobile-optimized components
|
||
├── utils/ # Web utilities
|
||
└── public/ # PWA assets (manifest, icons, service worker)
|
||
```
|
||
|
||
### Mobile Components (`src/web/mobile/`)
|
||
|
||
| Component | Purpose |
|
||
|-----------|---------|
|
||
| `App.tsx` | Main mobile app shell |
|
||
| `TabBar.tsx` | Bottom navigation bar |
|
||
| `SessionPillBar.tsx` | Horizontal session selector |
|
||
| `CommandInputBar.tsx` | Message input with voice support |
|
||
| `MessageHistory.tsx` | Conversation display |
|
||
| `ResponseViewer.tsx` | AI response viewer |
|
||
| `AutoRunIndicator.tsx` | Auto Run status display |
|
||
| `MobileHistoryPanel.tsx` | Command history browser |
|
||
| `QuickActionsMenu.tsx` | Quick action shortcuts |
|
||
| `SlashCommandAutocomplete.tsx` | Command autocomplete |
|
||
| `ConnectionStatusIndicator.tsx` | WebSocket connection status |
|
||
| `OfflineQueueBanner.tsx` | Offline message queue indicator |
|
||
|
||
### Web Hooks (`src/web/hooks/`)
|
||
|
||
| Hook | Purpose |
|
||
|------|---------|
|
||
| `useWebSocket.ts` | WebSocket connection management |
|
||
| `useSessions.ts` | Session state synchronization |
|
||
| `useCommandHistory.ts` | Command history management |
|
||
| `useSwipeGestures.ts` | Touch gesture handling |
|
||
| `usePullToRefresh.ts` | Pull-to-refresh functionality |
|
||
| `useOfflineQueue.ts` | Offline message queuing |
|
||
| `useNotifications.ts` | Push notification handling |
|
||
| `useDeviceColorScheme.ts` | System theme detection |
|
||
| `useUnreadBadge.ts` | Unread message badge |
|
||
| `useSwipeUp.ts` | Swipe-up gesture detection |
|
||
|
||
### Communication
|
||
|
||
The web interface communicates with the desktop app via WebSocket:
|
||
|
||
```typescript
|
||
// Desktop broadcasts to web clients
|
||
window.maestro.web.broadcastUserInput(sessionId, input);
|
||
window.maestro.web.broadcastAutoRunState(sessionId, state);
|
||
window.maestro.web.broadcastTabChange(sessionId, tabId);
|
||
|
||
// Web client sends commands back
|
||
websocket.send({ type: 'command', sessionId, content });
|
||
```
|
||
|
||
### PWA Features
|
||
|
||
- Installable as standalone app
|
||
- Service worker for offline support
|
||
- Push notifications
|
||
- Responsive design for phones and tablets
|
||
|
||
---
|
||
|
||
## CLI Tool
|
||
|
||
Command-line interface for headless Maestro operations.
|
||
|
||
### Architecture
|
||
|
||
```
|
||
src/cli/
|
||
├── index.ts # CLI entry point
|
||
├── commands/ # Command implementations
|
||
│ ├── list-agents.ts
|
||
│ ├── list-groups.ts
|
||
│ ├── list-playbooks.ts
|
||
│ ├── run-playbook.ts
|
||
│ ├── show-agent.ts
|
||
│ └── show-playbook.ts
|
||
├── output/ # Output formatters
|
||
│ ├── formatter.ts # Human-readable output
|
||
│ └── jsonl.ts # JSONL streaming output
|
||
└── services/ # CLI-specific services
|
||
├── agent-spawner.ts # Agent process management
|
||
├── batch-processor.ts # Batch execution logic
|
||
├── playbooks.ts # Playbook operations
|
||
└── storage.ts # Data persistence
|
||
```
|
||
|
||
### Available Commands
|
||
|
||
| Command | Description |
|
||
|---------|-------------|
|
||
| `maestro list-agents` | List available AI agents |
|
||
| `maestro list-groups` | List session groups |
|
||
| `maestro list-playbooks` | List saved playbooks |
|
||
| `maestro show-agent <id>` | Show agent details |
|
||
| `maestro show-playbook <id>` | Show playbook configuration |
|
||
| `maestro run-playbook <id>` | Execute a playbook |
|
||
|
||
### Output Formats
|
||
|
||
- **Human-readable**: Formatted tables and text (default)
|
||
- **JSONL**: Streaming JSON lines for programmatic use (`--format jsonl`)
|
||
|
||
---
|
||
|
||
## Shared Module
|
||
|
||
Cross-platform code shared between main process, renderer, web, and CLI.
|
||
|
||
### Files
|
||
|
||
```
|
||
src/shared/
|
||
├── index.ts # Re-exports
|
||
├── types.ts # Shared type definitions
|
||
├── theme-types.ts # Theme interface shared across platforms
|
||
├── cli-activity.ts # CLI activity tracking types
|
||
└── templateVariables.ts # Template variable utilities
|
||
```
|
||
|
||
### Theme Types
|
||
|
||
```typescript
|
||
// Shared theme interface used by renderer, main (web server), and web client
|
||
interface Theme {
|
||
id: ThemeId;
|
||
name: string;
|
||
mode: 'light' | 'dark' | 'vibe';
|
||
colors: ThemeColors;
|
||
}
|
||
```
|
||
|
||
### Template Variables
|
||
|
||
Utilities for processing template variables in Custom AI Commands:
|
||
|
||
```typescript
|
||
// Available variables
|
||
{{date}} // YYYY-MM-DD
|
||
{{time}} // HH:MM:SS
|
||
{{datetime}} // Combined
|
||
{{cwd}} // Working directory
|
||
{{session}} // Session name
|
||
{{agent}} // Agent type
|
||
```
|
||
|
||
---
|
||
|
||
## Remote Access & Tunnels
|
||
|
||
Secure remote access to Maestro via Cloudflare Tunnels.
|
||
|
||
### Architecture
|
||
|
||
| Component | Purpose |
|
||
|-----------|---------|
|
||
| `tunnel-manager.ts` | Manages cloudflared process lifecycle |
|
||
| `utils/cliDetection.ts` | Detects cloudflared installation |
|
||
| `utils/networkUtils.ts` | Local IP detection for LAN access |
|
||
|
||
### Tunnel Manager
|
||
|
||
```typescript
|
||
interface TunnelStatus {
|
||
active: boolean;
|
||
url?: string; // Public tunnel URL
|
||
error?: string;
|
||
}
|
||
|
||
// IPC API
|
||
window.maestro.tunnel.start();
|
||
window.maestro.tunnel.stop();
|
||
window.maestro.tunnel.getStatus();
|
||
window.maestro.tunnel.onStatusChange(callback);
|
||
```
|
||
|
||
### Access Methods
|
||
|
||
1. **Local Network**: Direct IP access on same network
|
||
2. **Cloudflare Tunnel**: Secure public URL for remote access
|
||
|
||
### Security
|
||
|
||
- Tunnels require cloudflared CLI installed
|
||
- URLs are temporary and change on restart
|
||
- No authentication by default (consider network security)
|
||
|
||
---
|
||
|
||
## Error Handling Patterns
|
||
|
||
### IPC Handlers (Main Process)
|
||
|
||
**Pattern 1: Throw for critical failures**
|
||
```typescript
|
||
ipcMain.handle('process:spawn', async (_, config) => {
|
||
if (!processManager) throw new Error('Process manager not initialized');
|
||
return processManager.spawn(config);
|
||
});
|
||
```
|
||
|
||
**Pattern 2: Try-catch with boolean return**
|
||
```typescript
|
||
ipcMain.handle('git:isRepo', async (_, cwd) => {
|
||
try {
|
||
const result = await execFileNoThrow('git', ['rev-parse', '--is-inside-work-tree'], cwd);
|
||
return result.exitCode === 0;
|
||
} catch {
|
||
return false;
|
||
}
|
||
});
|
||
```
|
||
|
||
### Services (Renderer)
|
||
|
||
**Pattern: Never throw, return safe defaults**
|
||
```typescript
|
||
export const gitService = {
|
||
async isRepo(cwd: string): Promise<boolean> {
|
||
try {
|
||
return await window.maestro.git.isRepo(cwd);
|
||
} catch (error) {
|
||
console.error('Git isRepo error:', error);
|
||
return false;
|
||
}
|
||
},
|
||
};
|
||
```
|
||
|
||
### React Components
|
||
|
||
**Pattern: Try-catch with user-friendly errors**
|
||
```typescript
|
||
const handleFileLoad = async (path: string) => {
|
||
try {
|
||
const content = await window.maestro.fs.readFile(path);
|
||
setFileContent(content);
|
||
} catch (error) {
|
||
console.error('Failed to load file:', error);
|
||
setError('Failed to load file');
|
||
}
|
||
};
|
||
```
|
||
|
||
### Summary
|
||
|
||
| Layer | Pattern |
|
||
|-------|---------|
|
||
| IPC Handlers | Throw critical, catch optional |
|
||
| Services | Never throw, safe defaults |
|
||
| ProcessManager | Throw spawn failures, emit runtime events |
|
||
| Components | Try-catch async, show UI errors |
|
||
| Hooks | Internal catch, expose error state |
|