feat: add configurable sandbox mode setting

Add a global setting to enable/disable sandbox mode for Claude Agent SDK.
This allows users to control sandbox behavior based on their authentication
setup and system compatibility.

Changes:
- Add enableSandboxMode to GlobalSettings (default: true)
- Add sandbox mode checkbox in Claude settings UI
- Wire up setting through app store and settings service
- Update createChatOptions and createAutoModeOptions to use setting
- Add getEnableSandboxModeSetting helper function
- Remove hardcoded sandbox configuration from ClaudeProvider
- Add detailed logging throughout agent execution flow

The sandbox mode requires API key or OAuth token authentication. Users
experiencing issues with CLI-only auth can disable it in settings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Stephan Rieche
2025-12-27 12:24:28 +01:00
parent 0fe6a12d20
commit 920dcd105f
11 changed files with 308 additions and 26 deletions

View File

@@ -23,6 +23,8 @@ export class ClaudeProvider extends BaseProvider {
* Execute a query using Claude Agent SDK
*/
async *executeQuery(options: ExecuteOptions): AsyncGenerator<ProviderMessage> {
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<any>;
@@ -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;
}
}