diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 6b63ffa8..188e2883 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -190,12 +190,31 @@ server.on('upgrade', (request, socket, head) => { // Events WebSocket connection handler wss.on('connection', (ws: WebSocket) => { - console.log('[WebSocket] Client connected'); + console.log('[WebSocket] Client connected, ready state:', ws.readyState); // Subscribe to all events and forward to this client const unsubscribe = events.subscribe((type, payload) => { + console.log('[WebSocket] Event received:', { + type, + hasPayload: !!payload, + payloadKeys: payload ? Object.keys(payload) : [], + wsReadyState: ws.readyState, + wsOpen: ws.readyState === WebSocket.OPEN, + }); + if (ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify({ type, payload })); + const message = JSON.stringify({ type, payload }); + console.log('[WebSocket] Sending event to client:', { + type, + messageLength: message.length, + sessionId: (payload as any)?.sessionId, + }); + ws.send(message); + } else { + console.log( + '[WebSocket] WARNING: Cannot send event, WebSocket not open. ReadyState:', + ws.readyState + ); } }); @@ -205,7 +224,7 @@ wss.on('connection', (ws: WebSocket) => { }); ws.on('error', (error) => { - console.error('[WebSocket] Error:', error); + console.error('[WebSocket] ERROR:', error); unsubscribe(); }); }); diff --git a/apps/server/src/lib/sdk-options.ts b/apps/server/src/lib/sdk-options.ts index e7fc3578..80433f5b 100644 --- a/apps/server/src/lib/sdk-options.ts +++ b/apps/server/src/lib/sdk-options.ts @@ -216,6 +216,9 @@ export interface CreateSdkOptionsConfig { /** Enable auto-loading of CLAUDE.md files via SDK's settingSources */ autoLoadClaudeMd?: boolean; + + /** Enable sandbox mode for bash command isolation */ + enableSandboxMode?: boolean; } /** @@ -314,7 +317,7 @@ export function createSuggestionsOptions(config: CreateSdkOptionsConfig): Option * - Full tool access for code modification * - Standard turns for interactive sessions * - Model priority: explicit model > session model > chat default - * - Sandbox enabled for bash safety + * - Sandbox mode controlled by enableSandboxMode setting * - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading */ export function createChatOptions(config: CreateSdkOptionsConfig): Options { @@ -333,10 +336,12 @@ export function createChatOptions(config: CreateSdkOptionsConfig): Options { maxTurns: MAX_TURNS.standard, cwd: config.cwd, allowedTools: [...TOOL_PRESETS.chat], - sandbox: { - enabled: true, - autoAllowBashIfSandboxed: true, - }, + ...(config.enableSandboxMode && { + sandbox: { + enabled: true, + autoAllowBashIfSandboxed: true, + }, + }), ...claudeMdOptions, ...(config.abortController && { abortController: config.abortController }), }; @@ -349,7 +354,7 @@ export function createChatOptions(config: CreateSdkOptionsConfig): Options { * - Full tool access for code modification and implementation * - Extended turns for thorough feature implementation * - Uses default model (can be overridden) - * - Sandbox enabled for bash safety + * - Sandbox mode controlled by enableSandboxMode setting * - When autoLoadClaudeMd is true, uses preset mode and settingSources for CLAUDE.md loading */ export function createAutoModeOptions(config: CreateSdkOptionsConfig): Options { @@ -365,10 +370,12 @@ export function createAutoModeOptions(config: CreateSdkOptionsConfig): Options { maxTurns: MAX_TURNS.maximum, cwd: config.cwd, allowedTools: [...TOOL_PRESETS.fullAccess], - sandbox: { - enabled: true, - autoAllowBashIfSandboxed: true, - }, + ...(config.enableSandboxMode && { + sandbox: { + enabled: true, + autoAllowBashIfSandboxed: true, + }, + }), ...claudeMdOptions, ...(config.abortController && { abortController: config.abortController }), }; diff --git a/apps/server/src/lib/settings-helpers.ts b/apps/server/src/lib/settings-helpers.ts index 9c4456ff..8c6e3073 100644 --- a/apps/server/src/lib/settings-helpers.ts +++ b/apps/server/src/lib/settings-helpers.ts @@ -45,6 +45,34 @@ export async function getAutoLoadClaudeMdSetting( } } +/** + * Get the enableSandboxMode setting from global settings. + * Returns false if settings service is not available. + * + * @param settingsService - Optional settings service instance + * @param logPrefix - Prefix for log messages (e.g., '[AgentService]') + * @returns Promise resolving to the enableSandboxMode setting value + */ +export async function getEnableSandboxModeSetting( + settingsService?: SettingsService | null, + logPrefix = '[SettingsHelper]' +): Promise { + if (!settingsService) { + console.log(`${logPrefix} SettingsService not available, sandbox mode disabled`); + return false; + } + + try { + const globalSettings = await settingsService.getGlobalSettings(); + const result = globalSettings.enableSandboxMode ?? false; + console.log(`${logPrefix} enableSandboxMode from global settings: ${result}`); + return result; + } catch (error) { + console.error(`${logPrefix} Failed to load enableSandboxMode setting:`, error); + throw error; + } +} + /** * Filters out CLAUDE.md from context files when autoLoadClaudeMd is enabled * and rebuilds the formatted prompt without it. diff --git a/apps/server/src/providers/claude-provider.ts b/apps/server/src/providers/claude-provider.ts index 9237cdf6..563dd70c 100644 --- a/apps/server/src/providers/claude-provider.ts +++ b/apps/server/src/providers/claude-provider.ts @@ -23,6 +23,8 @@ export class ClaudeProvider extends BaseProvider { * Execute a query using Claude Agent SDK */ async *executeQuery(options: ExecuteOptions): AsyncGenerator { + console.log('[ClaudeProvider] executeQuery() called'); + const { prompt, model, @@ -35,6 +37,20 @@ export class ClaudeProvider extends BaseProvider { sdkSessionId, } = options; + console.log('[ClaudeProvider] Options:', { + model, + cwd, + maxTurns, + promptType: typeof prompt, + promptLength: typeof prompt === 'string' ? prompt.length : 'array', + hasSystemPrompt: !!systemPrompt, + systemPromptLength: systemPrompt?.length, + hasConversationHistory: !!conversationHistory?.length, + conversationHistoryLength: conversationHistory?.length || 0, + sdkSessionId, + allowedToolsCount: allowedTools?.length, + }); + // Build Claude SDK options const defaultTools = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch']; const toolsToUse = allowedTools || defaultTools; @@ -45,11 +61,7 @@ export class ClaudeProvider extends BaseProvider { maxTurns, cwd, allowedTools: toolsToUse, - permissionMode: 'acceptEdits', - sandbox: { - enabled: true, - autoAllowBashIfSandboxed: true, - }, + permissionMode: 'default', abortController, // Resume existing SDK session if we have a session ID ...(sdkSessionId && conversationHistory && conversationHistory.length > 0 @@ -59,6 +71,15 @@ export class ClaudeProvider extends BaseProvider { ...(options.settingSources && { settingSources: options.settingSources }), }; + console.log('[ClaudeProvider] SDK options prepared:', { + model: sdkOptions.model, + maxTurns: sdkOptions.maxTurns, + permissionMode: sdkOptions.permissionMode, + sandboxEnabled: sdkOptions.sandbox?.enabled || false, + hasResume: !!(sdkOptions as any).resume, + toolsCount: sdkOptions.allowedTools?.length, + }); + // Build prompt payload let promptPayload: string | AsyncIterable; @@ -83,14 +104,84 @@ export class ClaudeProvider extends BaseProvider { // Execute via Claude Agent SDK try { - const stream = query({ prompt: promptPayload, options: sdkOptions }); + console.log('[ClaudeProvider] ANTHROPIC_API_KEY exists:', !!process.env.ANTHROPIC_API_KEY); + console.log( + '[ClaudeProvider] ANTHROPIC_API_KEY length:', + process.env.ANTHROPIC_API_KEY?.length || 0 + ); + console.log('[ClaudeProvider] HOME directory:', process.env.HOME); + console.log('[ClaudeProvider] User:', process.env.USER); + console.log('[ClaudeProvider] Current working directory:', process.cwd()); - // Stream messages directly - they're already in the correct format - for await (const msg of stream) { - yield msg as ProviderMessage; + // CRITICAL DEBUG: Log exact SDK options being passed + console.log('[ClaudeProvider] EXACT sdkOptions being passed to query():'); + console.log( + JSON.stringify( + { + model: sdkOptions.model, + maxTurns: sdkOptions.maxTurns, + cwd: sdkOptions.cwd, + allowedTools: sdkOptions.allowedTools, + permissionMode: sdkOptions.permissionMode, + hasSandbox: !!sdkOptions.sandbox, + hasAbortController: !!sdkOptions.abortController, + hasResume: !!(sdkOptions as any).resume, + hasSettingSources: !!sdkOptions.settingSources, + settingSources: sdkOptions.settingSources, + }, + null, + 2 + ) + ); + + console.log('[ClaudeProvider] Calling Claude Agent SDK query()...'); + console.log( + '[ClaudeProvider] About to call query() with prompt payload type:', + typeof promptPayload + ); + + const stream = query({ prompt: promptPayload, options: sdkOptions }); + console.log('[ClaudeProvider] query() call returned, stream object type:', typeof stream); + + console.log('[ClaudeProvider] SDK query() returned stream, starting iteration...'); + let streamMessageCount = 0; + + // Add a watchdog timer to detect if stream is hanging + let lastMessageTime = Date.now(); + const watchdogInterval = setInterval(() => { + const timeSinceLastMessage = Date.now() - lastMessageTime; + if (timeSinceLastMessage > 10000) { + console.log( + `[ClaudeProvider] WARNING: No messages received for ${Math.floor(timeSinceLastMessage / 1000)}s` + ); + } + }, 5000); + + try { + // Stream messages directly - they're already in the correct format + for await (const msg of stream) { + lastMessageTime = Date.now(); + streamMessageCount++; + console.log(`[ClaudeProvider] Stream message #${streamMessageCount}:`, { + type: msg.type, + subtype: (msg as any).subtype, + hasMessage: !!(msg as any).message, + hasResult: !!(msg as any).result, + session_id: msg.session_id, + }); + yield msg as ProviderMessage; + } + } finally { + clearInterval(watchdogInterval); } + + console.log( + '[ClaudeProvider] Stream iteration completed, total messages:', + streamMessageCount + ); } catch (error) { - console.error('[ClaudeProvider] executeQuery() error during execution:', error); + console.error('[ClaudeProvider] ERROR: executeQuery() error during execution:', error); + console.error('[ClaudeProvider] ERROR stack:', (error as Error).stack); throw error; } } diff --git a/apps/server/src/routes/agent/routes/send.ts b/apps/server/src/routes/agent/routes/send.ts index 0dd2f424..35c1e88a 100644 --- a/apps/server/src/routes/agent/routes/send.ts +++ b/apps/server/src/routes/agent/routes/send.ts @@ -19,7 +19,16 @@ export function createSendHandler(agentService: AgentService) { model?: string; }; + console.log('[Send Handler] Received request:', { + sessionId, + messageLength: message?.length, + workingDirectory, + imageCount: imagePaths?.length || 0, + model, + }); + if (!sessionId || !message) { + console.log('[Send Handler] ERROR: Validation failed - missing sessionId or message'); res.status(400).json({ success: false, error: 'sessionId and message are required', @@ -27,6 +36,8 @@ export function createSendHandler(agentService: AgentService) { return; } + console.log('[Send Handler] Validation passed, calling agentService.sendMessage()'); + // Start the message processing (don't await - it streams via WebSocket) agentService .sendMessage({ @@ -37,12 +48,16 @@ export function createSendHandler(agentService: AgentService) { model, }) .catch((error) => { + console.error('[Send Handler] ERROR: Background error in sendMessage():', error); logError(error, 'Send message failed (background)'); }); + console.log('[Send Handler] Returning immediate response to client'); + // Return immediately - responses come via WebSocket res.json({ success: true, message: 'Message sent' }); } catch (error) { + console.error('[Send Handler] ERROR: Synchronous error:', error); logError(error, 'Send message failed'); res.status(500).json({ success: false, error: getErrorMessage(error) }); } diff --git a/apps/server/src/services/agent-service.ts b/apps/server/src/services/agent-service.ts index 5afddcd9..966519d4 100644 --- a/apps/server/src/services/agent-service.ts +++ b/apps/server/src/services/agent-service.ts @@ -17,7 +17,11 @@ import { ProviderFactory } from '../providers/provider-factory.js'; import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js'; import { PathNotAllowedError } from '@automaker/platform'; import type { SettingsService } from './settings-service.js'; -import { getAutoLoadClaudeMdSetting, filterClaudeMdFromContext } from '../lib/settings-helpers.js'; +import { + getAutoLoadClaudeMdSetting, + getEnableSandboxModeSetting, + filterClaudeMdFromContext, +} from '../lib/settings-helpers.js'; interface Message { id: string; @@ -140,12 +144,29 @@ export class AgentService { imagePaths?: string[]; model?: string; }) { + console.log('[AgentService] sendMessage() called:', { + sessionId, + messageLength: message?.length, + workingDirectory, + imageCount: imagePaths?.length || 0, + model, + }); + const session = this.sessions.get(sessionId); if (!session) { + console.error('[AgentService] ERROR: Session not found:', sessionId); throw new Error(`Session ${sessionId} not found`); } + console.log('[AgentService] Session found:', { + sessionId, + messageCount: session.messages.length, + isRunning: session.isRunning, + workingDirectory: session.workingDirectory, + }); + if (session.isRunning) { + console.error('[AgentService] ERROR: Agent already running for session:', sessionId); throw new Error('Agent is already processing a message'); } @@ -192,16 +213,19 @@ export class AgentService { session.abortController = new AbortController(); // Emit started event so UI can show thinking indicator + console.log('[AgentService] Emitting "started" event for session:', sessionId); this.emitAgentEvent(sessionId, { type: 'started', }); // Emit user message event + console.log('[AgentService] Emitting "message" event for session:', sessionId); this.emitAgentEvent(sessionId, { type: 'message', message: userMessage, }); + console.log('[AgentService] Saving session messages'); await this.saveSession(sessionId, session.messages); try { @@ -215,6 +239,12 @@ export class AgentService { '[AgentService]' ); + // Load enableSandboxMode setting (global setting only) + const enableSandboxMode = await getEnableSandboxModeSetting( + this.settingsService, + '[AgentService]' + ); + // Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.) const contextResult = await loadContextFiles({ projectPath: effectiveWorkDir, @@ -239,6 +269,7 @@ export class AgentService { systemPrompt: combinedSystemPrompt, abortController: session.abortController!, autoLoadClaudeMd, + enableSandboxMode, }); // Extract model, maxTurns, and allowedTools from SDK options @@ -247,6 +278,7 @@ export class AgentService { const allowedTools = sdkOptions.allowedTools as string[] | undefined; // Get provider for this model + console.log('[AgentService] Getting provider for model:', effectiveModel); const provider = ProviderFactory.getProviderForModel(effectiveModel); console.log( @@ -267,6 +299,7 @@ export class AgentService { sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming }; + console.log('[AgentService] Building prompt with images...'); // Build prompt content with images const { content: promptContent } = await buildPromptWithImages( message, @@ -278,14 +311,32 @@ export class AgentService { // Set the prompt in options options.prompt = promptContent; + console.log('[AgentService] Executing query via provider:', { + model: effectiveModel, + promptLength: typeof promptContent === 'string' ? promptContent.length : 'array', + hasConversationHistory: !!conversationHistory.length, + sdkSessionId: session.sdkSessionId, + }); + // Execute via provider const stream = provider.executeQuery(options); + console.log('[AgentService] Stream created, starting to iterate...'); let currentAssistantMessage: Message | null = null; let responseText = ''; const toolUses: Array<{ name: string; input: unknown }> = []; + let messageCount = 0; + console.log('[AgentService] Entering stream loop...'); for await (const msg of stream) { + messageCount++; + console.log(`[AgentService] Stream message #${messageCount}:`, { + type: msg.type, + subtype: (msg as any).subtype, + hasContent: !!(msg as any).message?.content, + session_id: msg.session_id, + }); + // Capture SDK session ID from any message and persist it if (msg.session_id && !session.sdkSessionId) { session.sdkSessionId = msg.session_id; @@ -295,6 +346,7 @@ export class AgentService { } if (msg.type === 'assistant') { + console.log('[AgentService] Processing assistant message...'); if (msg.message?.content) { for (const block of msg.message.content) { if (block.type === 'text') { @@ -312,6 +364,10 @@ export class AgentService { currentAssistantMessage.content = responseText; } + console.log( + '[AgentService] Emitting "stream" event, text length:', + responseText.length + ); this.emitAgentEvent(sessionId, { type: 'stream', messageId: currentAssistantMessage.id, @@ -325,6 +381,7 @@ export class AgentService { }; toolUses.push(toolUse); + console.log('[AgentService] Tool use detected:', toolUse.name); this.emitAgentEvent(sessionId, { type: 'tool_use', tool: toolUse, @@ -333,6 +390,7 @@ export class AgentService { } } } else if (msg.type === 'result') { + console.log('[AgentService] Result message received, subtype:', (msg as any).subtype); if (msg.subtype === 'success' && msg.result) { if (currentAssistantMessage) { currentAssistantMessage.content = msg.result; @@ -340,6 +398,7 @@ export class AgentService { } } + console.log('[AgentService] Emitting "complete" event'); this.emitAgentEvent(sessionId, { type: 'complete', messageId: currentAssistantMessage?.id, @@ -349,6 +408,8 @@ export class AgentService { } } + console.log('[AgentService] Stream loop completed, total messages:', messageCount); + await this.saveSession(sessionId, session.messages); session.isRunning = false; @@ -757,7 +818,13 @@ export class AgentService { } private emitAgentEvent(sessionId: string, data: Record): void { + console.log('[AgentService] emitAgentEvent() called:', { + sessionId, + eventType: data.type, + dataKeys: Object.keys(data), + }); this.events.emit('agent:stream', { sessionId, ...data }); + console.log('[AgentService] Event emitted to EventEmitter'); } private getSystemPrompt(): string { diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts index bcdb92a8..c0ba7bfb 100644 --- a/apps/server/src/services/auto-mode-service.ts +++ b/apps/server/src/services/auto-mode-service.ts @@ -32,7 +32,11 @@ import { } from '../lib/sdk-options.js'; import { FeatureLoader } from './feature-loader.js'; import type { SettingsService } from './settings-service.js'; -import { getAutoLoadClaudeMdSetting, filterClaudeMdFromContext } from '../lib/settings-helpers.js'; +import { + getAutoLoadClaudeMdSetting, + getEnableSandboxModeSetting, + filterClaudeMdFromContext, +} from '../lib/settings-helpers.js'; const execAsync = promisify(exec); @@ -1833,12 +1837,16 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set. ? options.autoLoadClaudeMd : await getAutoLoadClaudeMdSetting(finalProjectPath, this.settingsService, '[AutoMode]'); + // Load enableSandboxMode setting (global setting only) + const enableSandboxMode = await getEnableSandboxModeSetting(this.settingsService, '[AutoMode]'); + // Build SDK options using centralized configuration for feature implementation const sdkOptions = createAutoModeOptions({ cwd: workDir, model: model, abortController, autoLoadClaudeMd, + enableSandboxMode, }); // Extract model, maxTurns, and allowedTools from SDK options diff --git a/apps/ui/src/components/views/settings-view.tsx b/apps/ui/src/components/views/settings-view.tsx index b888c9b6..6ea52add 100644 --- a/apps/ui/src/components/views/settings-view.tsx +++ b/apps/ui/src/components/views/settings-view.tsx @@ -50,6 +50,8 @@ export function SettingsView() { setValidationModel, autoLoadClaudeMd, setAutoLoadClaudeMd, + enableSandboxMode, + setEnableSandboxMode, } = useAppStore(); // Hide usage tracking when using API key (only show for Claude Code CLI users) @@ -108,6 +110,8 @@ export function SettingsView() { {showUsageTracking && } diff --git a/apps/ui/src/components/views/settings-view/claude/claude-md-settings.tsx b/apps/ui/src/components/views/settings-view/claude/claude-md-settings.tsx index 920984be..c2a6a3db 100644 --- a/apps/ui/src/components/views/settings-view/claude/claude-md-settings.tsx +++ b/apps/ui/src/components/views/settings-view/claude/claude-md-settings.tsx @@ -1,11 +1,13 @@ import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; -import { FileCode } from 'lucide-react'; +import { FileCode, Shield } from 'lucide-react'; import { cn } from '@/lib/utils'; interface ClaudeMdSettingsProps { autoLoadClaudeMd: boolean; onAutoLoadClaudeMdChange: (enabled: boolean) => void; + enableSandboxMode: boolean; + onEnableSandboxModeChange: (enabled: boolean) => void; } /** @@ -25,6 +27,8 @@ interface ClaudeMdSettingsProps { export function ClaudeMdSettings({ autoLoadClaudeMd, onAutoLoadClaudeMdChange, + enableSandboxMode, + onEnableSandboxModeChange, }: ClaudeMdSettingsProps) { return (
+ +
+ onEnableSandboxModeChange(checked === true)} + className="mt-1" + data-testid="enable-sandbox-mode-checkbox" + /> +
+ +

+ Run bash commands in an isolated sandbox environment for additional security. + + Note: On some systems, enabling sandbox mode may cause the agent to hang without + responding. If you experience issues, try disabling this option. + +

+
+
); diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index 874e1a6d..c4d7e2ca 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -480,6 +480,7 @@ export interface AppState { // Claude Agent SDK Settings autoLoadClaudeMd: boolean; // Auto-load CLAUDE.md files using SDK's settingSources option + enableSandboxMode: boolean; // Enable sandbox mode for bash commands (may cause issues on some systems) // Project Analysis projectAnalysis: ProjectAnalysis | null; @@ -756,6 +757,7 @@ export interface AppActions { // Claude Agent SDK Settings actions setAutoLoadClaudeMd: (enabled: boolean) => Promise; + setEnableSandboxMode: (enabled: boolean) => Promise; // AI Profile actions addAIProfile: (profile: Omit) => void; @@ -929,6 +931,7 @@ const initialState: AppState = { enhancementModel: 'sonnet', // Default to sonnet for feature enhancement validationModel: 'opus', // Default to opus for GitHub issue validation autoLoadClaudeMd: false, // Default to disabled (user must opt-in) + enableSandboxMode: true, // Default to enabled for security (can be disabled if issues occur) aiProfiles: DEFAULT_AI_PROFILES, projectAnalysis: null, isAnalyzing: false, @@ -1561,6 +1564,12 @@ export const useAppStore = create()( const { syncSettingsToServer } = await import('@/hooks/use-settings-migration'); await syncSettingsToServer(); }, + setEnableSandboxMode: async (enabled) => { + set({ enableSandboxMode: enabled }); + // Sync to server settings file + const { syncSettingsToServer } = await import('@/hooks/use-settings-migration'); + await syncSettingsToServer(); + }, // AI Profile actions addAIProfile: (profile) => { @@ -2706,6 +2715,7 @@ export const useAppStore = create()( enhancementModel: state.enhancementModel, validationModel: state.validationModel, autoLoadClaudeMd: state.autoLoadClaudeMd, + enableSandboxMode: state.enableSandboxMode, // Profiles and sessions aiProfiles: state.aiProfiles, chatSessions: state.chatSessions, diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts index e73e7269..a0a58e21 100644 --- a/libs/types/src/settings.ts +++ b/libs/types/src/settings.ts @@ -301,6 +301,8 @@ export interface GlobalSettings { // Claude Agent SDK Settings /** Auto-load CLAUDE.md files using SDK's settingSources option */ autoLoadClaudeMd?: boolean; + /** Enable sandbox mode for bash commands (default: true, disable if issues occur) */ + enableSandboxMode?: boolean; } /** @@ -459,6 +461,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = { worktreePanelCollapsed: false, lastSelectedSessionByProject: {}, autoLoadClaudeMd: false, + enableSandboxMode: true, }; /** Default credentials (empty strings - user must provide API keys) */