Skip to content

Latest commit

 

History

History
1663 lines (1295 loc) · 54 KB

File metadata and controls

1663 lines (1295 loc) · 54 KB

Architecture Guide

Deep technical documentation for Maestro's architecture and design patterns. For quick reference, see CLAUDE.md. For development setup, see CONTRIBUTING.md.

Table of Contents


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.

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
Loading

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)
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:

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

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

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

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

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

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:

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.

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.

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)

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)

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:

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

// 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

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:

style={{ color: theme.colors.textMain }}  // Correct

Use Tailwind for layout:

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:
const [mySetting, setMySettingState] = useState<MyType>(defaultValue);
  1. Create wrapper function:
const setMySetting = (value: MyType) => {
  setMySettingState(value);
  window.maestro.settings.set('mySetting', value);
};
  1. Load in useEffect:
const saved = await window.maestro.settings.get('mySetting');
if (saved !== undefined) setMySettingState(saved);
  1. 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

// 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

// 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:

// 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

// 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:

// 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

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

// 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

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:

// 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

// 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

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

// 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:

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

// 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:

// 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

// 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

// 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:

// 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

// 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:

// 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

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

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

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

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

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