mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
## CHANGES
- Added Sentry crash reporting for error tracking and debugging 🐛 - Implemented opt-out privacy setting for anonymous crash reports 🔒 - Enhanced agent spawning with generic config options for session continuity 🔧 - Fixed tab creation null safety checks across the codebase 🛡️ - Added Edit Agent action to Quick Actions modal for faster access ⚡ - Enabled bookmark toggle directly from Quick Actions menu 📌 - Improved batch processing to run in read-only mode by default 📝 - Cleaned up queued items display by removing redundant tab indicators 🧹 - Strengthened null checks in tab helper functions for stability 💪 - Updated version to 0.9.1 with comprehensive bug fixes and improvements 🚀
This commit is contained in:
912
package-lock.json
generated
912
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "maestro",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.1",
|
||||
"description": "Run AI coding agents autonomously for days.",
|
||||
"main": "dist/main/index.js",
|
||||
"author": {
|
||||
@@ -150,6 +150,7 @@
|
||||
"@fastify/rate-limit": "^9.1.0",
|
||||
"@fastify/static": "^7.0.4",
|
||||
"@fastify/websocket": "^9.0.0",
|
||||
"@sentry/electron": "^7.5.0",
|
||||
"@tanstack/react-virtual": "^3.13.13",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"adm-zip": "^0.5.16",
|
||||
|
||||
@@ -785,10 +785,12 @@ describe('QuickActionsModal', () => {
|
||||
});
|
||||
render(<QuickActionsModal {...props} />);
|
||||
|
||||
// Filter to just sessions so we can reliably test Cmd+0
|
||||
const input = screen.getByPlaceholderText('Type a command or jump to agent...');
|
||||
fireEvent.change(input, { target: { value: 'Session' } });
|
||||
fireEvent.keyDown(input, { key: '0', metaKey: true });
|
||||
|
||||
// Should trigger the 10th item
|
||||
// Should trigger the 10th session (Session 9 due to alphabetical sorting)
|
||||
expect(props.setActiveSessionId).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,6 +62,7 @@ const AGENT_DEFINITIONS: Omit<AgentConfig, 'available' | 'path' | 'capabilities'
|
||||
// YOLO mode (--dangerously-skip-permissions) is always enabled - Maestro requires it
|
||||
args: ['--print', '--verbose', '--output-format', 'stream-json', '--dangerously-skip-permissions'],
|
||||
resumeArgs: (sessionId: string) => ['--resume', sessionId], // Resume with session ID
|
||||
readOnlyArgs: ['--permission-mode', 'plan'], // Read-only/plan mode
|
||||
},
|
||||
{
|
||||
id: 'codex',
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from 'path';
|
||||
import os from 'os';
|
||||
import fs from 'fs/promises';
|
||||
import fsSync from 'fs';
|
||||
import * as Sentry from '@sentry/electron/main';
|
||||
import { ProcessManager } from './process-manager';
|
||||
import { WebServer } from './web-server';
|
||||
import { AgentDetector } from './agent-detector';
|
||||
@@ -17,6 +18,32 @@ import { initializeOutputParsers } from './parsers';
|
||||
import { DEMO_MODE, DEMO_DATA_PATH } from './constants';
|
||||
import { initAutoUpdater } from './auto-updater';
|
||||
|
||||
// Initialize Sentry for crash reporting (before app.ready)
|
||||
// Check if crash reporting is enabled (default: true for opt-out behavior)
|
||||
const crashReportingStore = new Store<{ crashReportingEnabled: boolean }>({
|
||||
name: 'maestro-settings',
|
||||
});
|
||||
const crashReportingEnabled = crashReportingStore.get('crashReportingEnabled', true);
|
||||
|
||||
if (crashReportingEnabled) {
|
||||
Sentry.init({
|
||||
dsn: 'https://2303c5f787f910863d83ed5d27ce8ed2@o4510554134740992.ingest.us.sentry.io/4510554135789568',
|
||||
// Set release version for better debugging
|
||||
release: app.getVersion(),
|
||||
// Only send errors, not performance data
|
||||
tracesSampleRate: 0,
|
||||
// Filter out sensitive data
|
||||
beforeSend(event) {
|
||||
// Remove any potential sensitive data from the event
|
||||
if (event.user) {
|
||||
delete event.user.ip_address;
|
||||
delete event.user.email;
|
||||
}
|
||||
return event;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Demo mode: use a separate data directory for fresh demos
|
||||
if (DEMO_MODE) {
|
||||
app.setPath('userData', DEMO_DATA_PATH);
|
||||
|
||||
@@ -94,7 +94,10 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void
|
||||
agentId: agent?.id,
|
||||
agentCommand: agent?.command,
|
||||
agentPath: agent?.path,
|
||||
hasAgentSessionId: !!config.agentSessionId
|
||||
hasAgentSessionId: !!config.agentSessionId,
|
||||
hasPrompt: !!config.prompt,
|
||||
promptLength: config.prompt?.length,
|
||||
promptValue: config.prompt,
|
||||
});
|
||||
let finalArgs = [...config.args];
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ export default function MaestroConsole() {
|
||||
audioFeedbackCommand, setAudioFeedbackCommand,
|
||||
toastDuration, setToastDuration,
|
||||
checkForUpdatesOnStartup, setCheckForUpdatesOnStartup,
|
||||
crashReportingEnabled, setCrashReportingEnabled,
|
||||
shortcuts, setShortcuts,
|
||||
customAICommands, setCustomAICommands,
|
||||
globalStats, updateGlobalStats,
|
||||
@@ -1671,12 +1672,9 @@ export default function MaestroConsole() {
|
||||
// Create a new tab in the session to start fresh
|
||||
setSessions(prev => prev.map(s => {
|
||||
if (s.id !== sessionId) return s;
|
||||
const newTab = createTab();
|
||||
return {
|
||||
...s,
|
||||
aiTabs: [...s.aiTabs, newTab],
|
||||
activeTabId: newTab.id,
|
||||
};
|
||||
const result = createTab(s);
|
||||
if (!result) return s;
|
||||
return result.session;
|
||||
}));
|
||||
|
||||
// Focus the input after creating new tab
|
||||
@@ -3434,8 +3432,8 @@ export default function MaestroConsole() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Build spawn args with resume if we have an agent session ID
|
||||
// Use the ACTIVE TAB's agentSessionId (not the deprecated session-level one)
|
||||
// Get the ACTIVE TAB's agentSessionId for session continuity
|
||||
// (not the deprecated session-level one)
|
||||
const activeTab = getActiveTab(session);
|
||||
const tabAgentSessionId = activeTab?.agentSessionId;
|
||||
const isReadOnly = activeTab?.readOnlyMode;
|
||||
@@ -3451,14 +3449,9 @@ export default function MaestroConsole() {
|
||||
)
|
||||
: [...agent.args];
|
||||
|
||||
if (tabAgentSessionId) {
|
||||
spawnArgs.push('--resume', tabAgentSessionId);
|
||||
}
|
||||
|
||||
// Add read-only/plan mode if the active tab has readOnlyMode enabled
|
||||
if (isReadOnly) {
|
||||
spawnArgs.push('--permission-mode', 'plan');
|
||||
}
|
||||
// Note: agentSessionId and readOnlyMode are passed to spawn() config below.
|
||||
// The main process uses agent-specific argument builders (resumeArgs, readOnlyArgs)
|
||||
// to construct the correct CLI args for each agent type.
|
||||
|
||||
// Include tab ID in targetSessionId for proper output routing
|
||||
const targetSessionId = `${sessionId}-ai-${activeTab?.id || 'default'}`;
|
||||
@@ -3526,7 +3519,10 @@ export default function MaestroConsole() {
|
||||
cwd: session.cwd,
|
||||
command: commandToUse,
|
||||
args: spawnArgs,
|
||||
prompt: promptToSend
|
||||
prompt: promptToSend,
|
||||
// Generic spawn options - main process builds agent-specific args
|
||||
agentSessionId: tabAgentSessionId,
|
||||
readOnlyMode: isReadOnly,
|
||||
});
|
||||
|
||||
console.log(`[Remote] ${session.toolType} spawn initiated successfully`);
|
||||
@@ -3618,8 +3614,8 @@ export default function MaestroConsole() {
|
||||
const agent = await window.maestro.agents.get(session.toolType);
|
||||
if (!agent) throw new Error(`Agent not found for toolType: ${session.toolType}`);
|
||||
|
||||
// Build spawn args with resume if we have a session ID
|
||||
// Use the TARGET TAB's agentSessionId (not the active tab or deprecated session-level one)
|
||||
// Get the TARGET TAB's agentSessionId for session continuity
|
||||
// (not the active tab or deprecated session-level one)
|
||||
const tabAgentSessionId = targetTab?.agentSessionId;
|
||||
const isReadOnly = item.readOnlyMode || targetTab?.readOnlyMode;
|
||||
|
||||
@@ -3634,15 +3630,9 @@ export default function MaestroConsole() {
|
||||
)
|
||||
: [...(agent.args || [])];
|
||||
|
||||
if (tabAgentSessionId) {
|
||||
spawnArgs.push('--resume', tabAgentSessionId);
|
||||
}
|
||||
|
||||
// Add read-only/plan mode if the queued item was from a read-only tab
|
||||
// or if the target tab currently has readOnlyMode enabled
|
||||
if (isReadOnly) {
|
||||
spawnArgs.push('--permission-mode', 'plan');
|
||||
}
|
||||
// Note: agentSessionId and readOnlyMode are passed to spawn() config below.
|
||||
// The main process uses agent-specific argument builders (resumeArgs, readOnlyArgs)
|
||||
// to construct the correct CLI args for each agent type.
|
||||
|
||||
const commandToUse = agent.path || agent.command;
|
||||
|
||||
@@ -3656,6 +3646,18 @@ export default function MaestroConsole() {
|
||||
// If user sends only an image without text, inject the default image-only prompt
|
||||
const effectivePrompt = isImageOnlyMessage ? DEFAULT_IMAGE_ONLY_PROMPT : item.text!;
|
||||
|
||||
console.log('[processQueuedItem] Spawning agent with queued message:', {
|
||||
sessionId: targetSessionId,
|
||||
toolType: session.toolType,
|
||||
prompt: effectivePrompt,
|
||||
promptLength: effectivePrompt?.length,
|
||||
hasAgentSessionId: !!tabAgentSessionId,
|
||||
agentSessionId: tabAgentSessionId,
|
||||
isReadOnly,
|
||||
argsLength: spawnArgs.length,
|
||||
args: spawnArgs,
|
||||
});
|
||||
|
||||
await window.maestro.process.spawn({
|
||||
sessionId: targetSessionId,
|
||||
toolType: session.toolType,
|
||||
@@ -3663,7 +3665,10 @@ export default function MaestroConsole() {
|
||||
command: commandToUse,
|
||||
args: spawnArgs,
|
||||
prompt: effectivePrompt,
|
||||
images: hasImages ? item.images : undefined
|
||||
images: hasImages ? item.images : undefined,
|
||||
// Generic spawn options - main process builds agent-specific args
|
||||
agentSessionId: tabAgentSessionId,
|
||||
readOnlyMode: isReadOnly,
|
||||
});
|
||||
} else if (item.type === 'command' && item.command) {
|
||||
// Process a slash command - find the matching custom AI command
|
||||
@@ -3711,7 +3716,10 @@ export default function MaestroConsole() {
|
||||
cwd: session.cwd,
|
||||
command: commandToUse,
|
||||
args: spawnArgs,
|
||||
prompt: substitutedPrompt
|
||||
prompt: substitutedPrompt,
|
||||
// Generic spawn options - main process builds agent-specific args
|
||||
agentSessionId: tabAgentSessionId,
|
||||
readOnlyMode: isReadOnly,
|
||||
});
|
||||
} else {
|
||||
// Unknown command - add error log
|
||||
@@ -4775,6 +4783,10 @@ export default function MaestroConsole() {
|
||||
setTourOpen(true);
|
||||
}}
|
||||
setFuzzyFileSearchOpen={setFuzzyFileSearchOpen}
|
||||
onEditAgent={(session) => {
|
||||
setEditAgentSession(session);
|
||||
setEditAgentModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{lightboxImage && (
|
||||
@@ -5179,6 +5191,7 @@ export default function MaestroConsole() {
|
||||
setSessions(prev => prev.map(s => {
|
||||
if (s.id !== activeSession.id) return s;
|
||||
const result = createTab(s, { saveToHistory: defaultSaveToHistory });
|
||||
if (!result) return s;
|
||||
return result.session;
|
||||
}));
|
||||
setActiveAgentSessionId(null);
|
||||
@@ -5377,6 +5390,7 @@ export default function MaestroConsole() {
|
||||
setSessions(prev => prev.map(s => {
|
||||
if (s.id !== activeSession.id) return s;
|
||||
const result = createTab(s, { saveToHistory: defaultSaveToHistory });
|
||||
if (!result) return s;
|
||||
return result.session;
|
||||
}));
|
||||
}}
|
||||
@@ -5963,6 +5977,8 @@ export default function MaestroConsole() {
|
||||
setToastDuration={setToastDuration}
|
||||
checkForUpdatesOnStartup={checkForUpdatesOnStartup}
|
||||
setCheckForUpdatesOnStartup={setCheckForUpdatesOnStartup}
|
||||
crashReportingEnabled={crashReportingEnabled}
|
||||
setCrashReportingEnabled={setCrashReportingEnabled}
|
||||
customAICommands={customAICommands}
|
||||
setCustomAICommands={setCustomAICommands}
|
||||
initialTab={settingsTab}
|
||||
|
||||
@@ -16,7 +16,7 @@ interface QueuedItemsListProps {
|
||||
/**
|
||||
* QueuedItemsList displays the execution queue with:
|
||||
* - Queued message separator with count
|
||||
* - Individual queued items (commands/messages) with tab indicators
|
||||
* - Individual queued items (commands/messages)
|
||||
* - Long message expand/collapse functionality
|
||||
* - Image attachment indicators
|
||||
* - Remove button with confirmation modal
|
||||
@@ -117,16 +117,6 @@ export const QueuedItemsList = memo(({
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
|
||||
{/* Tab indicator */}
|
||||
{item.tabName && (
|
||||
<div
|
||||
className="text-xs mb-1 font-mono"
|
||||
style={{ color: theme.colors.textDim }}
|
||||
>
|
||||
→ {item.tabName}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Item content */}
|
||||
<div
|
||||
className="text-sm pr-8 whitespace-pre-wrap break-words"
|
||||
|
||||
@@ -68,6 +68,7 @@ interface QuickActionsModalProps {
|
||||
setDebugWizardModalOpen?: (open: boolean) => void;
|
||||
startTour?: () => void;
|
||||
setFuzzyFileSearchOpen?: (open: boolean) => void;
|
||||
onEditAgent?: (session: Session) => void;
|
||||
}
|
||||
|
||||
export function QuickActionsModal(props: QuickActionsModalProps) {
|
||||
@@ -82,7 +83,7 @@ export function QuickActionsModal(props: QuickActionsModalProps) {
|
||||
setShortcutsHelpOpen, setAboutModalOpen, setLogViewerOpen, setProcessMonitorOpen,
|
||||
setAgentSessionsOpen, setActiveAgentSessionId, setGitDiffPreview, setGitLogOpen,
|
||||
onRenameTab, onToggleReadOnlyMode, onOpenTabSwitcher, tabShortcuts, isAiMode, setPlaygroundOpen, onRefreshGitFileState,
|
||||
onDebugReleaseQueuedItem, markdownEditMode, onToggleMarkdownEditMode, setUpdateCheckModalOpen, openWizard, wizardGoToStep, setDebugWizardModalOpen, startTour, setFuzzyFileSearchOpen
|
||||
onDebugReleaseQueuedItem, markdownEditMode, onToggleMarkdownEditMode, setUpdateCheckModalOpen, openWizard, wizardGoToStep, setDebugWizardModalOpen, startTour, setFuzzyFileSearchOpen, onEditAgent
|
||||
} = props;
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
@@ -204,6 +205,16 @@ export function QuickActionsModal(props: QuickActionsModalProps) {
|
||||
setRenameInstanceModalOpen(true);
|
||||
setQuickActionOpen(false);
|
||||
} }] : []),
|
||||
...(activeSession && onEditAgent ? [{ id: 'editAgent', label: `Edit Agent: ${activeSession.name}`, action: () => {
|
||||
onEditAgent(activeSession);
|
||||
setQuickActionOpen(false);
|
||||
} }] : []),
|
||||
...(activeSession ? [{ id: 'toggleBookmark', label: activeSession.bookmarked ? `Unbookmark: ${activeSession.name}` : `Bookmark: ${activeSession.name}`, action: () => {
|
||||
setSessions(prev => prev.map(s =>
|
||||
s.id === activeSessionId ? { ...s, bookmarked: !s.bookmarked } : s
|
||||
));
|
||||
setQuickActionOpen(false);
|
||||
} }] : []),
|
||||
...(activeSession?.groupId ? [{
|
||||
id: 'renameGroup',
|
||||
label: 'Rename Group',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect, useRef, memo } from 'react';
|
||||
import { X, Key, Moon, Sun, Keyboard, Check, Terminal, Bell, Cpu, Settings, Palette, Sparkles, History, Download } from 'lucide-react';
|
||||
import { X, Key, Moon, Sun, Keyboard, Check, Terminal, Bell, Cpu, Settings, Palette, Sparkles, History, Download, Bug } from 'lucide-react';
|
||||
import type { AgentConfig, Theme, ThemeColors, ThemeId, Shortcut, ShellInfo, CustomAICommand } from '../types';
|
||||
import { CustomThemeBuilder } from './CustomThemeBuilder';
|
||||
import { useLayerStack } from '../contexts/LayerStackContext';
|
||||
@@ -70,6 +70,8 @@ interface SettingsModalProps {
|
||||
setToastDuration: (value: number) => void;
|
||||
checkForUpdatesOnStartup: boolean;
|
||||
setCheckForUpdatesOnStartup: (value: boolean) => void;
|
||||
crashReportingEnabled: boolean;
|
||||
setCrashReportingEnabled: (value: boolean) => void;
|
||||
customAICommands: CustomAICommand[];
|
||||
setCustomAICommands: (commands: CustomAICommand[]) => void;
|
||||
initialTab?: 'general' | 'llm' | 'shortcuts' | 'theme' | 'notifications' | 'aicommands';
|
||||
@@ -871,6 +873,17 @@ export const SettingsModal = memo(function SettingsModal(props: SettingsModalPro
|
||||
onChange={props.setCheckForUpdatesOnStartup}
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
{/* Crash Reporting */}
|
||||
<SettingCheckbox
|
||||
icon={Bug}
|
||||
sectionLabel="Privacy"
|
||||
title="Send anonymous crash reports"
|
||||
description="Help improve Maestro by automatically sending crash reports. No personal data is collected. Changes take effect after restart."
|
||||
checked={props.crashReportingEnabled}
|
||||
onChange={props.setCrashReportingEnabled}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -299,17 +299,16 @@ export function useAgentExecution(
|
||||
// Spawn the agent for batch processing
|
||||
// Use effectiveCwd which may be a worktree path for parallel execution
|
||||
const commandToUse = agent.path || agent.command;
|
||||
// Only add Claude-specific permission-mode flag for Claude Code
|
||||
const spawnArgs = session.toolType === 'claude-code'
|
||||
? [...(agent.args || []), '--permission-mode', 'plan']
|
||||
: [...(agent.args || [])];
|
||||
// Batch processing runs in read-only mode (plan mode) to prevent unintended writes
|
||||
// The main process uses agent-specific readOnlyArgs builders for correct CLI args
|
||||
window.maestro.process.spawn({
|
||||
sessionId: targetSessionId,
|
||||
toolType: session.toolType,
|
||||
cwd: effectiveCwd,
|
||||
command: commandToUse,
|
||||
args: spawnArgs,
|
||||
prompt
|
||||
args: agent.args || [],
|
||||
prompt,
|
||||
readOnlyMode: true, // Batch operations run in read-only/plan mode
|
||||
}).catch(() => {
|
||||
cleanup();
|
||||
resolve({ success: false });
|
||||
|
||||
@@ -261,15 +261,16 @@ export function useAgentSessionManagement(
|
||||
if (s.id !== activeSession.id) return s;
|
||||
|
||||
// Create tab from the CURRENT session state (not stale closure value)
|
||||
const { session: updatedSession } = createTab(s, {
|
||||
const result = createTab(s, {
|
||||
agentSessionId,
|
||||
logs: messages,
|
||||
name,
|
||||
starred: isStarred,
|
||||
saveToHistory: defaultSaveToHistory
|
||||
});
|
||||
if (!result) return s;
|
||||
|
||||
return { ...updatedSession, inputMode: 'ai' };
|
||||
return { ...result.session, inputMode: 'ai' };
|
||||
}));
|
||||
setActiveAgentSessionId(agentSessionId);
|
||||
} catch (error) {
|
||||
|
||||
@@ -308,12 +308,14 @@ export function useMainKeyboardHandler(): UseMainKeyboardHandlerReturn {
|
||||
if (ctx.isTabShortcut(e, 'newTab')) {
|
||||
e.preventDefault();
|
||||
const result = ctx.createTab(ctx.activeSession, { saveToHistory: ctx.defaultSaveToHistory });
|
||||
ctx.setSessions((prev: Session[]) => prev.map((s: Session) =>
|
||||
s.id === ctx.activeSession!.id ? result.session : s
|
||||
));
|
||||
// Auto-focus the input so user can start typing immediately
|
||||
ctx.setActiveFocus('main');
|
||||
setTimeout(() => ctx.inputRef.current?.focus(), 50);
|
||||
if (result) {
|
||||
ctx.setSessions((prev: Session[]) => prev.map((s: Session) =>
|
||||
s.id === ctx.activeSession!.id ? result.session : s
|
||||
));
|
||||
// Auto-focus the input so user can start typing immediately
|
||||
ctx.setActiveFocus('main');
|
||||
setTimeout(() => ctx.inputRef.current?.focus(), 50);
|
||||
}
|
||||
}
|
||||
if (ctx.isTabShortcut(e, 'closeTab')) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -265,6 +265,7 @@ export function useRemoteIntegration(deps: UseRemoteIntegrationDeps): UseRemoteI
|
||||
|
||||
// Use createTab helper
|
||||
const result = createTab(s, { saveToHistory: defaultSaveToHistory });
|
||||
if (!result) return s;
|
||||
newTabId = result.tab.id;
|
||||
return result.session;
|
||||
}));
|
||||
|
||||
@@ -152,6 +152,10 @@ export interface UseSettingsReturn {
|
||||
checkForUpdatesOnStartup: boolean;
|
||||
setCheckForUpdatesOnStartup: (value: boolean) => void;
|
||||
|
||||
// Crash reporting settings
|
||||
crashReportingEnabled: boolean;
|
||||
setCrashReportingEnabled: (value: boolean) => void;
|
||||
|
||||
// Log Viewer settings
|
||||
logViewerSelectedLevels: string[];
|
||||
setLogViewerSelectedLevels: (value: string[]) => void;
|
||||
@@ -272,6 +276,9 @@ export function useSettings(): UseSettingsReturn {
|
||||
// Update Config
|
||||
const [checkForUpdatesOnStartup, setCheckForUpdatesOnStartupState] = useState(true); // Default: on
|
||||
|
||||
// Crash Reporting Config
|
||||
const [crashReportingEnabled, setCrashReportingEnabledState] = useState(true); // Default: on (opt-out)
|
||||
|
||||
// Log Viewer Config
|
||||
const [logViewerSelectedLevels, setLogViewerSelectedLevelsState] = useState<string[]>(['debug', 'info', 'warn', 'error', 'toast']);
|
||||
|
||||
@@ -453,6 +460,11 @@ export function useSettings(): UseSettingsReturn {
|
||||
window.maestro.settings.set('checkForUpdatesOnStartup', value);
|
||||
}, []);
|
||||
|
||||
const setCrashReportingEnabled = useCallback((value: boolean) => {
|
||||
setCrashReportingEnabledState(value);
|
||||
window.maestro.settings.set('crashReportingEnabled', value);
|
||||
}, []);
|
||||
|
||||
const setLogViewerSelectedLevels = useCallback((value: string[]) => {
|
||||
setLogViewerSelectedLevelsState(value);
|
||||
window.maestro.settings.set('logViewerSelectedLevels', value);
|
||||
@@ -887,6 +899,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
const savedAudioFeedbackCommand = await window.maestro.settings.get('audioFeedbackCommand');
|
||||
const savedToastDuration = await window.maestro.settings.get('toastDuration');
|
||||
const savedCheckForUpdatesOnStartup = await window.maestro.settings.get('checkForUpdatesOnStartup');
|
||||
const savedCrashReportingEnabled = await window.maestro.settings.get('crashReportingEnabled');
|
||||
const savedLogViewerSelectedLevels = await window.maestro.settings.get('logViewerSelectedLevels');
|
||||
const savedCustomAICommands = await window.maestro.settings.get('customAICommands');
|
||||
const savedGlobalStats = await window.maestro.settings.get('globalStats');
|
||||
@@ -929,6 +942,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
if (savedAudioFeedbackCommand !== undefined) setAudioFeedbackCommandState(savedAudioFeedbackCommand);
|
||||
if (savedToastDuration !== undefined) setToastDurationState(savedToastDuration);
|
||||
if (savedCheckForUpdatesOnStartup !== undefined) setCheckForUpdatesOnStartupState(savedCheckForUpdatesOnStartup);
|
||||
if (savedCrashReportingEnabled !== undefined) setCrashReportingEnabledState(savedCrashReportingEnabled);
|
||||
if (savedLogViewerSelectedLevels !== undefined) setLogViewerSelectedLevelsState(savedLogViewerSelectedLevels);
|
||||
|
||||
// Merge saved shortcuts with defaults (in case new shortcuts were added)
|
||||
@@ -1113,6 +1127,8 @@ export function useSettings(): UseSettingsReturn {
|
||||
setToastDuration,
|
||||
checkForUpdatesOnStartup,
|
||||
setCheckForUpdatesOnStartup,
|
||||
crashReportingEnabled,
|
||||
setCrashReportingEnabled,
|
||||
logViewerSelectedLevels,
|
||||
setLogViewerSelectedLevels,
|
||||
shortcuts,
|
||||
@@ -1184,6 +1200,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
audioFeedbackCommand,
|
||||
toastDuration,
|
||||
checkForUpdatesOnStartup,
|
||||
crashReportingEnabled,
|
||||
logViewerSelectedLevels,
|
||||
shortcuts,
|
||||
customAICommands,
|
||||
@@ -1223,6 +1240,7 @@ export function useSettings(): UseSettingsReturn {
|
||||
setAudioFeedbackCommand,
|
||||
setToastDuration,
|
||||
setCheckForUpdatesOnStartup,
|
||||
setCrashReportingEnabled,
|
||||
setLogViewerSelectedLevels,
|
||||
setShortcuts,
|
||||
setCustomAICommands,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import * as Sentry from '@sentry/electron/renderer';
|
||||
import MaestroConsole from './App';
|
||||
import { ErrorBoundary } from './components/ErrorBoundary';
|
||||
import { LayerStackProvider } from './contexts/LayerStackContext';
|
||||
@@ -8,6 +9,12 @@ import { WizardProvider } from './components/Wizard';
|
||||
import { logger } from './utils/logger';
|
||||
import './index.css';
|
||||
|
||||
// Initialize Sentry for the renderer process
|
||||
// The main process handles the enabled/disabled check and initializes Sentry there
|
||||
// Renderer Sentry will automatically connect to main process Sentry
|
||||
// We initialize unconditionally here - if main process didn't init, this is a no-op
|
||||
Sentry.init({});
|
||||
|
||||
// Set up global error handlers for uncaught exceptions in renderer process
|
||||
window.addEventListener('error', (event: ErrorEvent) => {
|
||||
logger.error(
|
||||
|
||||
@@ -34,7 +34,7 @@ function hasDraft(tab: AITab): boolean {
|
||||
* const unreadTabs = getNavigableTabs(session, true);
|
||||
*/
|
||||
export function getNavigableTabs(session: Session, showUnreadOnly = false): AITab[] {
|
||||
if (!session.aiTabs || session.aiTabs.length === 0) {
|
||||
if (!session || !session.aiTabs || session.aiTabs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export function getNavigableTabs(session: Session, showUnreadOnly = false): AITa
|
||||
* @returns The active AITab or undefined if no tabs exist
|
||||
*/
|
||||
export function getActiveTab(session: Session): AITab | undefined {
|
||||
if (!session.aiTabs || session.aiTabs.length === 0) {
|
||||
if (!session || !session.aiTabs || session.aiTabs.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,11 @@ export interface CreateTabResult {
|
||||
* logs: existingLogs
|
||||
* });
|
||||
*/
|
||||
export function createTab(session: Session, options: CreateTabOptions = {}): CreateTabResult {
|
||||
export function createTab(session: Session, options: CreateTabOptions = {}): CreateTabResult | null {
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
agentSessionId = null,
|
||||
logs = [],
|
||||
@@ -170,7 +174,7 @@ export interface CloseTabResult {
|
||||
* }
|
||||
*/
|
||||
export function closeTab(session: Session, tabId: string): CloseTabResult | null {
|
||||
if (!session.aiTabs || session.aiTabs.length === 0) {
|
||||
if (!session || !session.aiTabs || session.aiTabs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -345,8 +349,8 @@ export interface SetActiveTabResult {
|
||||
* }
|
||||
*/
|
||||
export function setActiveTab(session: Session, tabId: string): SetActiveTabResult | null {
|
||||
// Validate that the tab exists
|
||||
if (!session.aiTabs || session.aiTabs.length === 0) {
|
||||
// Validate that the session and tab exists
|
||||
if (!session || !session.aiTabs || session.aiTabs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -388,7 +392,7 @@ export function setActiveTab(session: Session, tabId: string): SetActiveTabResul
|
||||
* }
|
||||
*/
|
||||
export function getWriteModeTab(session: Session): AITab | undefined {
|
||||
if (!session.aiTabs || session.aiTabs.length === 0) {
|
||||
if (!session || !session.aiTabs || session.aiTabs.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -416,7 +420,7 @@ export function getWriteModeTab(session: Session): AITab | undefined {
|
||||
* }
|
||||
*/
|
||||
export function getBusyTabs(session: Session): AITab[] {
|
||||
if (!session.aiTabs || session.aiTabs.length === 0) {
|
||||
if (!session || !session.aiTabs || session.aiTabs.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -439,7 +443,7 @@ export function getBusyTabs(session: Session): AITab[] {
|
||||
* }
|
||||
*/
|
||||
export function navigateToNextTab(session: Session, showUnreadOnly = false): SetActiveTabResult | null {
|
||||
if (!session.aiTabs || session.aiTabs.length < 2) {
|
||||
if (!session || !session.aiTabs || session.aiTabs.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -498,7 +502,7 @@ export function navigateToNextTab(session: Session, showUnreadOnly = false): Set
|
||||
* }
|
||||
*/
|
||||
export function navigateToPrevTab(session: Session, showUnreadOnly = false): SetActiveTabResult | null {
|
||||
if (!session.aiTabs || session.aiTabs.length < 2) {
|
||||
if (!session || !session.aiTabs || session.aiTabs.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -559,7 +563,7 @@ export function navigateToPrevTab(session: Session, showUnreadOnly = false): Set
|
||||
* }
|
||||
*/
|
||||
export function navigateToTabByIndex(session: Session, index: number, showUnreadOnly = false): SetActiveTabResult | null {
|
||||
if (!session.aiTabs || session.aiTabs.length === 0) {
|
||||
if (!session || !session.aiTabs || session.aiTabs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user