['--resume', sessionId], // Resume with session ID
+ readOnlyArgs: ['--permission-mode', 'plan'], // Read-only/plan mode
},
{
id: 'codex',
diff --git a/src/main/index.ts b/src/main/index.ts
index a40de6fe..db9612eb 100644
--- a/src/main/index.ts
+++ b/src/main/index.ts
@@ -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);
diff --git a/src/main/ipc/handlers/process.ts b/src/main/ipc/handlers/process.ts
index 23398be8..0de39013 100644
--- a/src/main/ipc/handlers/process.ts
+++ b/src/main/ipc/handlers/process.ts
@@ -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];
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index 0cfb7bc9..92ab40f1 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -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}
diff --git a/src/renderer/components/QueuedItemsList.tsx b/src/renderer/components/QueuedItemsList.tsx
index ef9f6ab4..de681f04 100644
--- a/src/renderer/components/QueuedItemsList.tsx
+++ b/src/renderer/components/QueuedItemsList.tsx
@@ -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(({
- {/* Tab indicator */}
- {item.tabName && (
-
- → {item.tabName}
-
- )}
-
{/* Item content */}
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',
diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx
index e6bf78cd..704bd7f6 100644
--- a/src/renderer/components/SettingsModal.tsx
+++ b/src/renderer/components/SettingsModal.tsx
@@ -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 */}
+
)}
diff --git a/src/renderer/hooks/useAgentExecution.ts b/src/renderer/hooks/useAgentExecution.ts
index 63b23b86..939da18a 100644
--- a/src/renderer/hooks/useAgentExecution.ts
+++ b/src/renderer/hooks/useAgentExecution.ts
@@ -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 });
diff --git a/src/renderer/hooks/useAgentSessionManagement.ts b/src/renderer/hooks/useAgentSessionManagement.ts
index 66b485de..9a39a514 100644
--- a/src/renderer/hooks/useAgentSessionManagement.ts
+++ b/src/renderer/hooks/useAgentSessionManagement.ts
@@ -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) {
diff --git a/src/renderer/hooks/useMainKeyboardHandler.ts b/src/renderer/hooks/useMainKeyboardHandler.ts
index 7db12f51..e041ee28 100644
--- a/src/renderer/hooks/useMainKeyboardHandler.ts
+++ b/src/renderer/hooks/useMainKeyboardHandler.ts
@@ -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();
diff --git a/src/renderer/hooks/useRemoteIntegration.ts b/src/renderer/hooks/useRemoteIntegration.ts
index 124dde76..e984c4ec 100644
--- a/src/renderer/hooks/useRemoteIntegration.ts
+++ b/src/renderer/hooks/useRemoteIntegration.ts
@@ -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;
}));
diff --git a/src/renderer/hooks/useSettings.ts b/src/renderer/hooks/useSettings.ts
index 0948d3b8..a5dc6fd3 100644
--- a/src/renderer/hooks/useSettings.ts
+++ b/src/renderer/hooks/useSettings.ts
@@ -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(['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,
diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx
index 1e101bcb..0e5b08d7 100644
--- a/src/renderer/main.tsx
+++ b/src/renderer/main.tsx
@@ -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(
diff --git a/src/renderer/utils/tabHelpers.ts b/src/renderer/utils/tabHelpers.ts
index d4d4390d..f7c31253 100644
--- a/src/renderer/utils/tabHelpers.ts
+++ b/src/renderer/utils/tabHelpers.ts
@@ -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;
}