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..dc057873 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 ?? true; + 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..716e94ca 100644 --- a/apps/server/src/providers/claude-provider.ts +++ b/apps/server/src/providers/claude-provider.ts @@ -45,11 +45,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 @@ -57,6 +53,8 @@ export class ClaudeProvider extends BaseProvider { : {}), // Forward settingSources for CLAUDE.md file loading ...(options.settingSources && { settingSources: options.settingSources }), + // Forward sandbox configuration + ...(options.sandbox && { sandbox: options.sandbox }), }; // Build prompt payload @@ -90,7 +88,8 @@ export class ClaudeProvider extends BaseProvider { yield msg as ProviderMessage; } } 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/providers/types.ts b/apps/server/src/providers/types.ts index 5a594361..17b45066 100644 --- a/apps/server/src/providers/types.ts +++ b/apps/server/src/providers/types.ts @@ -34,6 +34,7 @@ export interface ExecuteOptions { conversationHistory?: ConversationMessage[]; // Previous messages for context sdkSessionId?: string; // Claude SDK session ID for resuming conversations settingSources?: Array<'user' | 'project' | 'local'>; // Claude filesystem settings to load + sandbox?: { enabled: boolean; autoAllowBashIfSandboxed?: boolean }; // Sandbox configuration } /** 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..dc6d4594 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; @@ -142,10 +146,12 @@ export class AgentService { }) { const session = this.sessions.get(sessionId); if (!session) { + console.error('[AgentService] ERROR: Session not found:', sessionId); throw new Error(`Session ${sessionId} not found`); } if (session.isRunning) { + console.error('[AgentService] ERROR: Agent already running for session:', sessionId); throw new Error('Agent is already processing a message'); } @@ -215,6 +221,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 +251,7 @@ export class AgentService { systemPrompt: combinedSystemPrompt, abortController: session.abortController!, autoLoadClaudeMd, + enableSandboxMode, }); // Extract model, maxTurns, and allowedTools from SDK options @@ -249,10 +262,6 @@ export class AgentService { // Get provider for this model const provider = ProviderFactory.getProviderForModel(effectiveModel); - console.log( - `[AgentService] Using provider "${provider.getName()}" for model "${effectiveModel}"` - ); - // Build options for provider const options: ExecuteOptions = { prompt: '', // Will be set below based on images @@ -264,6 +273,7 @@ export class AgentService { abortController: session.abortController!, conversationHistory: conversationHistory.length > 0 ? conversationHistory : undefined, settingSources: sdkOptions.settingSources, + sandbox: sdkOptions.sandbox, // Pass sandbox configuration sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming }; @@ -289,7 +299,6 @@ export class AgentService { // Capture SDK session ID from any message and persist it if (msg.session_id && !session.sdkSessionId) { session.sdkSessionId = msg.session_id; - console.log(`[AgentService] Captured SDK session ID: ${msg.session_id}`); // Persist the SDK session ID to ensure conversation continuity across server restarts await this.updateSession(sessionId, { sdkSessionId: msg.session_id }); } @@ -737,8 +746,6 @@ export class AgentService { queue: session.promptQueue, }); - console.log(`[AgentService] Processing next queued prompt for session ${sessionId}`); - try { await this.sendMessage({ sessionId, diff --git a/apps/server/src/services/auto-mode-service.ts b/apps/server/src/services/auto-mode-service.ts index bcdb92a8..987554f2 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); @@ -1149,6 +1153,7 @@ Format your response as a structured markdown document.`; allowedTools: sdkOptions.allowedTools as string[], abortController, settingSources: sdkOptions.settingSources, + sandbox: sdkOptions.sandbox, // Pass sandbox configuration }; const stream = provider.executeQuery(options); @@ -1833,12 +1838,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 @@ -1879,6 +1888,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set. abortController, systemPrompt: sdkOptions.systemPrompt, settingSources: sdkOptions.settingSources, + sandbox: sdkOptions.sandbox, // Pass sandbox configuration }; // Execute via provider diff --git a/apps/server/tests/unit/lib/sdk-options.test.ts b/apps/server/tests/unit/lib/sdk-options.test.ts index c7324d6c..e5d4c7c0 100644 --- a/apps/server/tests/unit/lib/sdk-options.test.ts +++ b/apps/server/tests/unit/lib/sdk-options.test.ts @@ -179,7 +179,7 @@ describe('sdk-options.ts', () => { it('should create options with chat settings', async () => { const { createChatOptions, TOOL_PRESETS, MAX_TURNS } = await import('@/lib/sdk-options.js'); - const options = createChatOptions({ cwd: '/test/path' }); + const options = createChatOptions({ cwd: '/test/path', enableSandboxMode: true }); expect(options.cwd).toBe('/test/path'); expect(options.maxTurns).toBe(MAX_TURNS.standard); @@ -212,6 +212,27 @@ describe('sdk-options.ts', () => { expect(options.model).toBe('claude-sonnet-4-20250514'); }); + + it('should not set sandbox when enableSandboxMode is false', async () => { + const { createChatOptions } = await import('@/lib/sdk-options.js'); + + const options = createChatOptions({ + cwd: '/test/path', + enableSandboxMode: false, + }); + + expect(options.sandbox).toBeUndefined(); + }); + + it('should not set sandbox when enableSandboxMode is not provided', async () => { + const { createChatOptions } = await import('@/lib/sdk-options.js'); + + const options = createChatOptions({ + cwd: '/test/path', + }); + + expect(options.sandbox).toBeUndefined(); + }); }); describe('createAutoModeOptions', () => { @@ -219,7 +240,7 @@ describe('sdk-options.ts', () => { const { createAutoModeOptions, TOOL_PRESETS, MAX_TURNS } = await import('@/lib/sdk-options.js'); - const options = createAutoModeOptions({ cwd: '/test/path' }); + const options = createAutoModeOptions({ cwd: '/test/path', enableSandboxMode: true }); expect(options.cwd).toBe('/test/path'); expect(options.maxTurns).toBe(MAX_TURNS.maximum); @@ -252,6 +273,27 @@ describe('sdk-options.ts', () => { expect(options.abortController).toBe(abortController); }); + + it('should not set sandbox when enableSandboxMode is false', async () => { + const { createAutoModeOptions } = await import('@/lib/sdk-options.js'); + + const options = createAutoModeOptions({ + cwd: '/test/path', + enableSandboxMode: false, + }); + + expect(options.sandbox).toBeUndefined(); + }); + + it('should not set sandbox when enableSandboxMode is not provided', async () => { + const { createAutoModeOptions } = await import('@/lib/sdk-options.js'); + + const options = createAutoModeOptions({ + cwd: '/test/path', + }); + + expect(options.sandbox).toBeUndefined(); + }); }); describe('createCustomOptions', () => { diff --git a/apps/server/tests/unit/providers/claude-provider.test.ts b/apps/server/tests/unit/providers/claude-provider.test.ts index 45deb0d1..b06642e9 100644 --- a/apps/server/tests/unit/providers/claude-provider.test.ts +++ b/apps/server/tests/unit/providers/claude-provider.test.ts @@ -73,7 +73,7 @@ describe('claude-provider.ts', () => { maxTurns: 10, cwd: '/test/dir', allowedTools: ['Read', 'Write'], - permissionMode: 'acceptEdits', + permissionMode: 'default', }), }); }); @@ -100,7 +100,7 @@ describe('claude-provider.ts', () => { }); }); - it('should enable sandbox by default', async () => { + it('should pass sandbox configuration when provided', async () => { vi.mocked(sdk.query).mockReturnValue( (async function* () { yield { type: 'text', text: 'test' }; @@ -110,6 +110,10 @@ describe('claude-provider.ts', () => { const generator = provider.executeQuery({ prompt: 'Test', cwd: '/test', + sandbox: { + enabled: true, + autoAllowBashIfSandboxed: true, + }, }); await collectAsyncGenerator(generator); @@ -242,11 +246,21 @@ describe('claude-provider.ts', () => { }); await expect(collectAsyncGenerator(generator)).rejects.toThrow('SDK execution failed'); - expect(consoleErrorSpy).toHaveBeenCalledWith( - '[ClaudeProvider] executeQuery() error during execution:', + + // Should log error message + expect(consoleErrorSpy).toHaveBeenNthCalledWith( + 1, + '[ClaudeProvider] ERROR: executeQuery() error during execution:', testError ); + // Should log stack trace + expect(consoleErrorSpy).toHaveBeenNthCalledWith( + 2, + '[ClaudeProvider] ERROR stack:', + testError.stack + ); + consoleErrorSpy.mockRestore(); }); }); 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..ae5a67e4 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,30 +1,37 @@ 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; } /** * ClaudeMdSettings Component * - * UI control for the autoLoadClaudeMd setting which enables automatic loading - * of project instructions from .claude/CLAUDE.md files via the Claude Agent SDK. + * UI controls for Claude Agent SDK settings including: + * - Auto-loading of project instructions from .claude/CLAUDE.md files + * - Sandbox mode for isolated bash command execution * * Usage: * ```tsx * * ``` */ 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/hooks/use-settings-migration.ts b/apps/ui/src/hooks/use-settings-migration.ts index 2bca750b..1e989060 100644 --- a/apps/ui/src/hooks/use-settings-migration.ts +++ b/apps/ui/src/hooks/use-settings-migration.ts @@ -224,6 +224,7 @@ export async function syncSettingsToServer(): Promise { enhancementModel: state.enhancementModel, validationModel: state.validationModel, autoLoadClaudeMd: state.autoLoadClaudeMd, + enableSandboxMode: state.enableSandboxMode, keyboardShortcuts: state.keyboardShortcuts, aiProfiles: state.aiProfiles, projects: state.projects, 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/provider.ts b/libs/types/src/provider.ts index 53c92717..3dca3db5 100644 --- a/libs/types/src/provider.ts +++ b/libs/types/src/provider.ts @@ -43,6 +43,7 @@ export interface ExecuteOptions { conversationHistory?: ConversationMessage[]; // Previous messages for context sdkSessionId?: string; // Claude SDK session ID for resuming conversations settingSources?: Array<'user' | 'project' | 'local'>; // Sources for CLAUDE.md loading + sandbox?: { enabled: boolean; autoAllowBashIfSandboxed?: boolean }; // Sandbox configuration } /** 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) */ diff --git a/package-lock.json b/package-lock.json index a3e7176f..1b8b192c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -451,6 +451,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1034,6 +1035,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz", "integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1076,6 +1078,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -1896,7 +1899,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -1918,7 +1920,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1935,7 +1936,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -1950,7 +1950,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -2706,7 +2705,6 @@ "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">=18" } @@ -2831,7 +2829,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -2848,7 +2845,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -2865,7 +2861,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -2974,7 +2969,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -2997,7 +2991,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3020,7 +3013,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3106,7 +3098,6 @@ ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/runtime": "^1.7.0" }, @@ -3129,7 +3120,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3149,7 +3139,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3488,8 +3477,7 @@ "version": "16.0.10", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz", "integrity": "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { "version": "16.0.10", @@ -3503,7 +3491,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3520,7 +3507,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3537,7 +3523,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3554,7 +3539,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3571,7 +3555,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3588,7 +3571,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3605,7 +3587,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3622,7 +3603,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3713,6 +3693,7 @@ "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.57.0" }, @@ -5153,7 +5134,6 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -5487,6 +5467,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.141.6.tgz", "integrity": "sha512-qWFxi2D6eGc1L03RzUuhyEOplZ7Q6q62YOl7Of9Y0q4YjwQwxRm4zxwDVtvUIoy4RLVCpqp5UoE+Nxv2PY9trg==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.141.0", "@tanstack/react-store": "^0.8.0", @@ -6038,6 +6019,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6048,6 +6030,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6153,6 +6136,7 @@ "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", @@ -6646,7 +6630,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@xyflow/react": { "version": "12.10.0", @@ -6744,6 +6729,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6804,6 +6790,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7363,6 +7350,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7894,8 +7882,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", @@ -8181,8 +8168,7 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/cross-env": { "version": "10.1.0", @@ -8279,6 +8265,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -8580,6 +8567,7 @@ "integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.0.12", "builder-util": "26.0.11", @@ -8906,7 +8894,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -8927,7 +8914,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -9178,6 +9164,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -11083,7 +11070,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11145,7 +11131,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -13564,7 +13549,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -13581,7 +13565,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -13599,7 +13582,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -13788,6 +13770,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -13797,6 +13780,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -14146,7 +14130,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -14335,6 +14318,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.0.tgz", "integrity": "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -14383,7 +14367,6 @@ "hasInstallScript": true, "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", @@ -14434,7 +14417,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14457,7 +14439,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14480,7 +14461,6 @@ "os": [ "darwin" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14497,7 +14477,6 @@ "os": [ "darwin" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14514,7 +14493,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14531,7 +14509,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14548,7 +14525,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14565,7 +14541,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14582,7 +14557,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -14599,7 +14573,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14622,7 +14595,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14645,7 +14617,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14668,7 +14639,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14691,7 +14661,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14714,7 +14683,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15183,7 +15151,6 @@ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", - "peer": true, "dependencies": { "client-only": "0.0.1" }, @@ -15353,7 +15320,6 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -15417,7 +15383,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -15515,6 +15480,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -15719,6 +15685,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16090,6 +16057,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16179,7 +16147,8 @@ "resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz", "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", @@ -16205,6 +16174,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16247,6 +16217,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -16504,6 +16475,7 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" },