sandboxbox 3.0.49 ā 3.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/sandboxbox-settings.json +2 -17
- package/claude-settings.json +0 -85
- package/package/CLAUDE.md +0 -200
- package/package/Dockerfile +0 -95
- package/package/README.md +0 -242
- package/package/claude-settings.json +0 -85
- package/package/cli.js +0 -90
- package/package/package/CLAUDE.md +0 -200
- package/package/package/Dockerfile +0 -95
- package/package/package/README.md +0 -242
- package/package/package/claude-settings.json +0 -85
- package/package/package/cli.js +0 -90
- package/package/package/package.json +0 -39
- package/package/package/sandboxbox-3.0.45.tgz +0 -0
- package/package/package/sandboxbox-settings.json +0 -40
- package/package/package/test.txt +0 -1
- package/package/package/utils/claude-optimizer.js +0 -129
- package/package/package/utils/colors.js +0 -15
- package/package/package/utils/commands/claude.js +0 -501
- package/package/package/utils/commands/container.js +0 -60
- package/package/package/utils/commands/index.js +0 -23
- package/package/package/utils/sandbox.js +0 -341
- package/package/package/utils/system-optimizer.js +0 -231
- package/package/package/utils/ui.js +0 -38
- package/package/package.json +0 -39
- package/package/sandboxbox-3.0.45.tgz +0 -0
- package/package/sandboxbox-3.0.46.tgz +0 -0
- package/package/sandboxbox-settings.json +0 -40
- package/package/test.txt +0 -1
- package/package/utils/claude-optimizer.js +0 -129
- package/package/utils/colors.js +0 -15
- package/package/utils/commands/claude.js +0 -502
- package/package/utils/commands/container.js +0 -60
- package/package/utils/commands/index.js +0 -23
- package/package/utils/sandbox.js +0 -341
- package/package/utils/system-optimizer.js +0 -231
- package/package/utils/ui.js +0 -38
- package/sandboxbox-3.0.45.tgz +0 -0
- package/sandboxbox-3.0.46.tgz +0 -0
- package/sandboxbox-3.0.48.tgz +0 -0
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
3
|
-
|
|
4
|
-
export class ClaudeOptimizer {
|
|
5
|
-
constructor(sandboxDir) {
|
|
6
|
-
this.sandboxDir = sandboxDir;
|
|
7
|
-
this.claudeDir = join(sandboxDir, '.claude');
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Optimize Claude settings for faster startup
|
|
11
|
-
optimizeSettings() {
|
|
12
|
-
const settingsPath = join(this.claudeDir, 'settings.json');
|
|
13
|
-
|
|
14
|
-
// Load existing settings or create optimized defaults
|
|
15
|
-
let settings = {};
|
|
16
|
-
if (existsSync(settingsPath)) {
|
|
17
|
-
try {
|
|
18
|
-
settings = JSON.parse(require('fs').readFileSync(settingsPath, 'utf8'));
|
|
19
|
-
} catch (e) {
|
|
20
|
-
// Silently create default settings
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Apply startup optimizations - preserve bundled hooks!
|
|
25
|
-
const optimizedSettings = {
|
|
26
|
-
...settings,
|
|
27
|
-
// Reduce plugin timeouts
|
|
28
|
-
pluginTimeout: settings.pluginTimeout || 5000,
|
|
29
|
-
// Disable unnecessary features for faster startup
|
|
30
|
-
alwaysThinkingEnabled: settings.alwaysThinkingEnabled || false,
|
|
31
|
-
// Optimize plugin loading - preserve existing plugins
|
|
32
|
-
enabledPlugins: {
|
|
33
|
-
...settings.enabledPlugins,
|
|
34
|
-
// Only enable essential plugins for performance
|
|
35
|
-
'glootie-cc@anentrypoint-plugins': true
|
|
36
|
-
}
|
|
37
|
-
// Note: DO NOT override hooks - preserve bundled settings hooks!
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
// Ensure directory exists
|
|
41
|
-
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
42
|
-
|
|
43
|
-
// Write optimized settings
|
|
44
|
-
writeFileSync(settingsPath, JSON.stringify(optimizedSettings, null, 2));
|
|
45
|
-
|
|
46
|
-
return optimizedSettings;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Pre-warm plugin connections
|
|
50
|
-
async prewarmPlugins() {
|
|
51
|
-
|
|
52
|
-
const prewarmScript = `
|
|
53
|
-
# Pre-warm script for Claude plugins
|
|
54
|
-
echo "Pre-warming plugin connections..."
|
|
55
|
-
|
|
56
|
-
# Set environment for faster plugin discovery
|
|
57
|
-
export NODE_OPTIONS="--max-old-space-size=2048"
|
|
58
|
-
export CLAUDE_PLUGIN_TIMEOUT=5000
|
|
59
|
-
|
|
60
|
-
# Pre-warm glootie plugin
|
|
61
|
-
if command -v npx >/dev/null 2>&1; then
|
|
62
|
-
timeout 3s npx -y glootie-cc@anentrypoint-plugins --version >/dev/null 2>&1 &
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
echo "Plugin pre-warming complete"
|
|
66
|
-
`;
|
|
67
|
-
|
|
68
|
-
const scriptPath = join(this.sandboxDir, '.claude', 'prewarm.sh');
|
|
69
|
-
writeFileSync(scriptPath, prewarmScript);
|
|
70
|
-
|
|
71
|
-
return scriptPath;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Create optimized environment for Claude
|
|
75
|
-
createOptimizedEnv(baseEnv = {}) {
|
|
76
|
-
return {
|
|
77
|
-
...baseEnv,
|
|
78
|
-
// Performance optimizations
|
|
79
|
-
NODE_OPTIONS: "--max-old-space-size=2048",
|
|
80
|
-
CLAUDE_PLUGIN_TIMEOUT: "5000",
|
|
81
|
-
CLAUDE_CACHE_DIR: join(this.sandboxDir, '.cache', 'claude'),
|
|
82
|
-
// Network optimizations
|
|
83
|
-
NODE_TLS_REJECT_UNAUTHORIZED: "0",
|
|
84
|
-
// Plugin optimizations
|
|
85
|
-
MCP_TIMEOUT: "5000",
|
|
86
|
-
MCP_CONNECTION_TIMEOUT: "3000",
|
|
87
|
-
// Fast startup flags
|
|
88
|
-
CLAUDE_FAST_STARTUP: "1",
|
|
89
|
-
CLAUDE_MINIMAL_MODE: "1"
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Generate startup timing profile
|
|
94
|
-
async profileStartup(claudeCommand) {
|
|
95
|
-
console.log('š Profiling Claude startup...');
|
|
96
|
-
|
|
97
|
-
const profileScript = `
|
|
98
|
-
#!/bin/bash
|
|
99
|
-
echo "Starting Claude startup profile..."
|
|
100
|
-
start_time=$(date +%s%3N)
|
|
101
|
-
|
|
102
|
-
# Run Claude with timing
|
|
103
|
-
timeout 30s ${claudeCommand} --help >/dev/null 2>&1 &
|
|
104
|
-
claude_pid=$!
|
|
105
|
-
|
|
106
|
-
# Monitor startup progress
|
|
107
|
-
while kill -0 $claude_pid 2>/dev/null; do
|
|
108
|
-
current_time=$(date +%s%3N)
|
|
109
|
-
elapsed=$((current_time - start_time))
|
|
110
|
-
|
|
111
|
-
if [ $elapsed -gt 10000 ]; then
|
|
112
|
-
echo "ā ļø Startup taking longer than 10s..."
|
|
113
|
-
kill $claude_pid 2>/dev/null
|
|
114
|
-
break
|
|
115
|
-
fi
|
|
116
|
-
|
|
117
|
-
sleep 0.5
|
|
118
|
-
done
|
|
119
|
-
|
|
120
|
-
wait $claude_pid
|
|
121
|
-
end_time=$(date +%s%3N)
|
|
122
|
-
total_time=$((end_time - start_time))
|
|
123
|
-
|
|
124
|
-
echo "Claude startup completed in ${total_time}ms"
|
|
125
|
-
`;
|
|
126
|
-
|
|
127
|
-
return profileScript;
|
|
128
|
-
}
|
|
129
|
-
}
|
package/package/utils/colors.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Color utilities for CLI output
|
|
2
|
-
export const colors = {
|
|
3
|
-
red: '\x1b[31m',
|
|
4
|
-
green: '\x1b[32m',
|
|
5
|
-
yellow: '\x1b[33m',
|
|
6
|
-
blue: '\x1b[34m',
|
|
7
|
-
magenta: '\x1b[35m',
|
|
8
|
-
cyan: '\x1b[36m',
|
|
9
|
-
white: '\x1b[37m',
|
|
10
|
-
reset: '\x1b[0m'
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export function color(colorName, text) {
|
|
14
|
-
return `${colors[colorName]}${text}${colors.reset}`;
|
|
15
|
-
}
|
|
@@ -1,502 +0,0 @@
|
|
|
1
|
-
import { existsSync, writeFileSync, appendFileSync } from 'fs';
|
|
2
|
-
import { resolve, join } from 'path';
|
|
3
|
-
import { spawn, execSync } from 'child_process';
|
|
4
|
-
import { color } from '../colors.js';
|
|
5
|
-
import { createSandbox, createSandboxEnv } from '../sandbox.js';
|
|
6
|
-
// ClaudeOptimizer disabled to preserve bundled hooks
|
|
7
|
-
// import { ClaudeOptimizer } from '../claude-optimizer.js';
|
|
8
|
-
import { SystemOptimizer } from '../system-optimizer.js';
|
|
9
|
-
|
|
10
|
-
const ALLOWED_TOOLS = [
|
|
11
|
-
'Task', 'Bash', 'Glob', 'Grep', 'Read', 'Edit', 'Write', 'NotebookEdit',
|
|
12
|
-
'WebFetch', 'TodoWrite', 'WebSearch', 'BashOutput', 'KillShell',
|
|
13
|
-
'SlashCommand', 'ExitPlanMode', 'mcp__glootie__execute',
|
|
14
|
-
'mcp__glootie__ast_tool', 'mcp__glootie__caveat',
|
|
15
|
-
'mcp__playwright__browser_navigate', 'mcp__playwright__browser_snapshot',
|
|
16
|
-
'mcp__playwright__browser_click', 'mcp__playwright__browser_type',
|
|
17
|
-
'mcp__playwright__browser_evaluate', 'mcp__playwright__browser_close',
|
|
18
|
-
'mcp__vexify__search_code'
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
// Console output configuration
|
|
22
|
-
const MAX_CONSOLE_LINES = parseInt(process.env.SANDBOX_MAX_CONSOLE_LINES) || 5;
|
|
23
|
-
const MAX_LOG_ENTRY_LENGTH = parseInt(process.env.SANDBOX_MAX_LOG_LENGTH) || 200;
|
|
24
|
-
const ENABLE_FILE_LOGGING = process.env.SANDBOX_ENABLE_FILE_LOGGING === 'true';
|
|
25
|
-
const VERBOSE_OUTPUT = process.env.SANDBOX_VERBOSE === 'true' || process.argv.includes('--verbose');
|
|
26
|
-
global.toolCallLog = [];
|
|
27
|
-
global.logFileHandle = null;
|
|
28
|
-
global.pendingToolCalls = new Map(); // Track tool calls by ID for result matching
|
|
29
|
-
global.conversationalBuffer = ''; // Track conversational text between tool calls
|
|
30
|
-
|
|
31
|
-
// Helper function to extract tool metadata without showing actual content
|
|
32
|
-
function extractToolMetadata(toolUse) {
|
|
33
|
-
const metadata = {
|
|
34
|
-
name: toolUse.name || 'unknown',
|
|
35
|
-
id: toolUse.id || 'no-id',
|
|
36
|
-
inputCount: 0,
|
|
37
|
-
inputTypes: {},
|
|
38
|
-
inputSizes: {},
|
|
39
|
-
totalInputSize: 0
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
if (toolUse.input && typeof toolUse.input === 'object') {
|
|
43
|
-
metadata.inputCount = Object.keys(toolUse.input).length;
|
|
44
|
-
|
|
45
|
-
for (const [key, value] of Object.entries(toolUse.input)) {
|
|
46
|
-
const type = Array.isArray(value) ? 'array' : typeof value;
|
|
47
|
-
metadata.inputTypes[key] = type;
|
|
48
|
-
|
|
49
|
-
// Calculate sizes without exposing content
|
|
50
|
-
if (type === 'string') {
|
|
51
|
-
metadata.inputSizes[key] = `${value.length} chars`;
|
|
52
|
-
metadata.totalInputSize += value.length;
|
|
53
|
-
} else if (type === 'array') {
|
|
54
|
-
metadata.inputSizes[key] = `${value.length} items`;
|
|
55
|
-
metadata.totalInputSize += JSON.stringify(value).length;
|
|
56
|
-
} else if (type === 'object' && value !== null) {
|
|
57
|
-
const objSize = JSON.stringify(value).length;
|
|
58
|
-
metadata.inputSizes[key] = `${objSize} chars`;
|
|
59
|
-
metadata.totalInputSize += objSize;
|
|
60
|
-
} else {
|
|
61
|
-
metadata.inputSizes[key] = `${type}`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return metadata;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Helper function to extract tool result metadata
|
|
70
|
-
function extractResultMetadata(result) {
|
|
71
|
-
const metadata = {
|
|
72
|
-
type: 'unknown',
|
|
73
|
-
size: 0,
|
|
74
|
-
hasContent: false,
|
|
75
|
-
isToolResult: false,
|
|
76
|
-
isError: false
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
if (result && typeof result === 'object') {
|
|
80
|
-
metadata.isToolResult = result.type === 'tool_result';
|
|
81
|
-
metadata.isError = result.is_error || false;
|
|
82
|
-
|
|
83
|
-
if (result.content) {
|
|
84
|
-
metadata.hasContent = true;
|
|
85
|
-
|
|
86
|
-
if (typeof result.content === 'string') {
|
|
87
|
-
metadata.type = 'text';
|
|
88
|
-
metadata.size = result.content.length;
|
|
89
|
-
} else if (Array.isArray(result.content)) {
|
|
90
|
-
metadata.type = 'array';
|
|
91
|
-
metadata.size = result.content.length;
|
|
92
|
-
// Count items by type without showing content
|
|
93
|
-
const typeCounts = {};
|
|
94
|
-
result.content.forEach(item => {
|
|
95
|
-
const itemType = item?.type || 'unknown';
|
|
96
|
-
typeCounts[itemType] = (typeCounts[itemType] || 0) + 1;
|
|
97
|
-
});
|
|
98
|
-
metadata.itemTypes = typeCounts;
|
|
99
|
-
} else if (typeof result.content === 'object') {
|
|
100
|
-
metadata.type = 'object';
|
|
101
|
-
metadata.size = Object.keys(result.content).length;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return metadata;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Helper function to truncate text to a sensible length
|
|
111
|
-
function truncateText(text, maxLength = MAX_LOG_ENTRY_LENGTH) {
|
|
112
|
-
if (text.length <= maxLength) return text;
|
|
113
|
-
return text.substring(0, maxLength - 3) + '...';
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Enhanced tool logging with detailed metadata and length limiting
|
|
117
|
-
function logToolCall(toolName, action = 'call', toolUse = null, result = null) {
|
|
118
|
-
const shortTime = new Date().toLocaleTimeString();
|
|
119
|
-
let logEntry = `Tool: ${toolName}`;
|
|
120
|
-
|
|
121
|
-
// Add conversational text if available
|
|
122
|
-
if (global.conversationalBuffer.trim()) {
|
|
123
|
-
const truncatedText = truncateText(global.conversationalBuffer.trim(), 80);
|
|
124
|
-
logEntry += ` - "${truncatedText}"`;
|
|
125
|
-
global.conversationalBuffer = ''; // Clear buffer after using
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Add compact metadata
|
|
129
|
-
if (toolUse && action === 'call') {
|
|
130
|
-
const metadata = extractToolMetadata(toolUse);
|
|
131
|
-
const metaInfo = [];
|
|
132
|
-
|
|
133
|
-
if (metadata.inputCount > 0) {
|
|
134
|
-
metaInfo.push(`${metadata.inputCount} inputs`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (metadata.totalInputSize > 0) {
|
|
138
|
-
metaInfo.push(`${metadata.totalInputSize} chars`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (metaInfo.length > 0) {
|
|
142
|
-
logEntry += ` (${metaInfo.join(', ')})`;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Add result metadata
|
|
147
|
-
if (result && action === 'result') {
|
|
148
|
-
const metadata = extractResultMetadata(result);
|
|
149
|
-
const resultInfo = [];
|
|
150
|
-
|
|
151
|
-
if (metadata.hasContent) {
|
|
152
|
-
if (metadata.size > 0) {
|
|
153
|
-
if (metadata.type === 'text') {
|
|
154
|
-
resultInfo.push(`${metadata.size} chars`);
|
|
155
|
-
} else if (metadata.type === 'array') {
|
|
156
|
-
resultInfo.push(`${metadata.size} items`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (metadata.isError) {
|
|
161
|
-
resultInfo.push('ERROR');
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (resultInfo.length > 0) {
|
|
166
|
-
logEntry += ` ā ${resultInfo.join(', ')}`;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const finalLogEntry = `[${shortTime}] ${truncateText(logEntry)}`;
|
|
171
|
-
|
|
172
|
-
global.toolCallLog.push(finalLogEntry);
|
|
173
|
-
|
|
174
|
-
// Keep only the last MAX_CONSOLE_LINES for console display
|
|
175
|
-
if (global.toolCallLog.length > MAX_CONSOLE_LINES) {
|
|
176
|
-
global.toolCallLog = global.toolCallLog.slice(-MAX_CONSOLE_LINES);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Optionally log to file with full metadata
|
|
180
|
-
if (ENABLE_FILE_LOGGING) {
|
|
181
|
-
try {
|
|
182
|
-
if (!global.logFileHandle) {
|
|
183
|
-
const timestamp = new Date().toISOString();
|
|
184
|
-
const logFileName = `sandboxbox-tool-calls-${Date.now()}.log`;
|
|
185
|
-
writeFileSync(logFileName, `# SandboxBox Tool Calls Log\n# Started: ${timestamp}\n# Format: [timestamp] Tool: name - "text" (metadata)\n\n`);
|
|
186
|
-
global.logFileHandle = logFileName;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const timestamp = new Date().toISOString();
|
|
190
|
-
let fileLogEntry = `[${timestamp}] Tool: ${toolName}`;
|
|
191
|
-
|
|
192
|
-
if (global.conversationalBuffer.trim()) {
|
|
193
|
-
fileLogEntry += ` - "${global.conversationalBuffer.trim()}"`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (toolUse && action === 'call') {
|
|
197
|
-
const metadata = extractToolMetadata(toolUse);
|
|
198
|
-
fileLogEntry += `\n Input details: ${JSON.stringify(metadata.inputSizes, null, 2)}`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (result && action === 'result') {
|
|
202
|
-
const metadata = extractResultMetadata(result);
|
|
203
|
-
fileLogEntry += `\n Result details: ${JSON.stringify(metadata, null, 2)}`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
appendFileSync(global.logFileHandle, fileLogEntry + '\n');
|
|
207
|
-
} catch (error) {
|
|
208
|
-
// Don't fail if logging fails
|
|
209
|
-
console.log(color('yellow', `ā ļø Could not write to log file: ${error.message}`));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Function to log conversational text
|
|
215
|
-
function logConversationalText(text) {
|
|
216
|
-
if (text && text.trim()) {
|
|
217
|
-
global.conversationalBuffer += text + ' ';
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Helper function to display recent tool calls
|
|
222
|
-
function displayRecentToolCalls() {
|
|
223
|
-
if (global.toolCallLog.length > 0) {
|
|
224
|
-
console.log(color('cyan', `\nš§ Recent tool calls (showing last ${global.toolCallLog.length}):`));
|
|
225
|
-
global.toolCallLog.forEach(log => {
|
|
226
|
-
console.log(color('cyan', ` ${log}`));
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
export async function claudeCommand(projectDir, prompt, flags = {}) {
|
|
233
|
-
if (!existsSync(projectDir)) {
|
|
234
|
-
console.log(color('red', `ā Project directory not found: ${projectDir}`));
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Note: We don't require git repository anymore - the sandbox will initialize it if needed
|
|
239
|
-
|
|
240
|
-
// Silent start - only show conversation unless verbose
|
|
241
|
-
|
|
242
|
-
const startTime = Date.now();
|
|
243
|
-
if (VERBOSE_OUTPUT) console.log(color('cyan', 'ā±ļø Stage 1: Creating sandbox...'));
|
|
244
|
-
|
|
245
|
-
const { useHostSettings, headlessMode } = flags;
|
|
246
|
-
const { sandboxDir, cleanup } = createSandbox(projectDir, { useHostSettings, headlessMode });
|
|
247
|
-
const sandboxCreateTime = Date.now() - startTime;
|
|
248
|
-
if (VERBOSE_OUTPUT) console.log(color('green', `ā
Sandbox created in ${sandboxCreateTime}ms`));
|
|
249
|
-
|
|
250
|
-
process.on('SIGINT', cleanup);
|
|
251
|
-
process.on('SIGTERM', cleanup);
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
const envStartTime = Date.now();
|
|
255
|
-
if (VERBOSE_OUTPUT) console.log(color('cyan', 'ā±ļø Stage 2: Setting up environment...'));
|
|
256
|
-
|
|
257
|
-
// Apply Claude optimizations (disabled to preserve bundled hooks)
|
|
258
|
-
// const claudeOptimizer = new ClaudeOptimizer(sandboxDir);
|
|
259
|
-
// claudeOptimizer.optimizeSettings();
|
|
260
|
-
// await claudeOptimizer.prewarmPlugins();
|
|
261
|
-
|
|
262
|
-
// Apply system optimizations (with sudo access)
|
|
263
|
-
const systemOptimizer = new SystemOptimizer();
|
|
264
|
-
const systemOptimizationsApplied = await systemOptimizer.optimizeSystem();
|
|
265
|
-
|
|
266
|
-
// Create optimized environment
|
|
267
|
-
const baseEnv = createSandboxEnv(sandboxDir, {
|
|
268
|
-
CLAUDECODE: '1'
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const env = systemOptimizationsApplied
|
|
272
|
-
? { ...baseEnv, ...systemOptimizer.createOptimizedContainerEnv() }
|
|
273
|
-
: baseEnv; // Use base environment when optimizer is disabled
|
|
274
|
-
|
|
275
|
-
const envCreateTime = Date.now() - envStartTime;
|
|
276
|
-
if (VERBOSE_OUTPUT) console.log(color('green', `ā
Environment configured in ${envCreateTime}ms`));
|
|
277
|
-
|
|
278
|
-
if (systemOptimizationsApplied && VERBOSE_OUTPUT) {
|
|
279
|
-
console.log(color('yellow', `š System-level optimizations applied`));
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const claudeArgs = [
|
|
283
|
-
'--verbose',
|
|
284
|
-
'--output-format', 'stream-json',
|
|
285
|
-
'--permission-mode', 'bypassPermissions',
|
|
286
|
-
'--allowed-tools', ALLOWED_TOOLS.join(',')
|
|
287
|
-
];
|
|
288
|
-
|
|
289
|
-
return new Promise((resolve, reject) => {
|
|
290
|
-
const claudeStartTime = Date.now();
|
|
291
|
-
if (VERBOSE_OUTPUT) console.log(color('cyan', 'ā±ļø Stage 3: Starting Claude Code...'));
|
|
292
|
-
|
|
293
|
-
const workspacePath = join(sandboxDir, 'workspace');
|
|
294
|
-
|
|
295
|
-
// Handle headless mode Playwright MCP reconfiguration
|
|
296
|
-
if (headlessMode) {
|
|
297
|
-
if (VERBOSE_OUTPUT) console.log(color('yellow', 'š Configuring headless Playwright MCP...'));
|
|
298
|
-
|
|
299
|
-
try {
|
|
300
|
-
// Remove existing Playwright MCP (ignoring failures)
|
|
301
|
-
execSync('claude mcp remove playwright', {
|
|
302
|
-
cwd: workspacePath,
|
|
303
|
-
stdio: 'pipe',
|
|
304
|
-
shell: true
|
|
305
|
-
});
|
|
306
|
-
} catch (e) {
|
|
307
|
-
// Ignore removal failures
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
// Add headless Playwright MCP
|
|
312
|
-
execSync('claude mcp add --headless --no-sandbox --scope=user playwright npx u/playwright/mcp@latest', {
|
|
313
|
-
cwd: workspacePath,
|
|
314
|
-
stdio: VERBOSE_OUTPUT ? 'inherit' : 'pipe',
|
|
315
|
-
shell: true
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
if (VERBOSE_OUTPUT) console.log(color('green', 'ā
Headless Playwright MCP configured'));
|
|
319
|
-
} catch (e) {
|
|
320
|
-
console.log(color('red', `ā Failed to configure headless Playwright MCP: ${e.message}`));
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Modify the prompt to include directory change instruction
|
|
325
|
-
const modifiedPrompt = `You are working in a sandboxed environment. Your working directory is "${workspacePath}". All operations should be performed in this directory. ${prompt}`;
|
|
326
|
-
|
|
327
|
-
// Add network isolation environment variables for process-level isolation
|
|
328
|
-
const networkIsolatedEnv = {
|
|
329
|
-
...env,
|
|
330
|
-
// Force network isolation through environment variables
|
|
331
|
-
SANDBOX_NETWORK_ISOLATED: 'true',
|
|
332
|
-
// Add unique sandbox identifier for port isolation
|
|
333
|
-
SANDBOX_ID: Math.random().toString(36).substr(2, 9),
|
|
334
|
-
// Restrict network binding to localhost when possible
|
|
335
|
-
NODE_OPTIONS: (env.NODE_OPTIONS || '') + ' --no-force-async-hooks-checks'
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
const proc = spawn('claude', claudeArgs, {
|
|
339
|
-
cwd: workspacePath, // Set working directory directly
|
|
340
|
-
env: networkIsolatedEnv, // Use network-isolated environment
|
|
341
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
342
|
-
shell: false, // Don't use shell since we're setting cwd directly
|
|
343
|
-
detached: false
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
let claudeStarted = false;
|
|
347
|
-
let jsonBuffer = ''; // Buffer for incomplete JSON lines
|
|
348
|
-
|
|
349
|
-
function handleEvent(event) {
|
|
350
|
-
if (event.type === 'system' && event.subtype === 'init') {
|
|
351
|
-
if (!claudeStarted) {
|
|
352
|
-
const claudeCreateTime = Date.now() - claudeStartTime;
|
|
353
|
-
if (VERBOSE_OUTPUT) console.log(color('green', `ā
Claude Code started in ${claudeCreateTime}ms`));
|
|
354
|
-
claudeStarted = true;
|
|
355
|
-
}
|
|
356
|
-
if (VERBOSE_OUTPUT) console.log(color('green', `ā
Session started (${event.session_id.substring(0, 8)}...)`));
|
|
357
|
-
if (VERBOSE_OUTPUT) console.log(color('cyan', `š¦ Model: ${event.model}`));
|
|
358
|
-
|
|
359
|
-
// Simple tool count warning only if less than 15 tools
|
|
360
|
-
if (event.tools.length < 15) {
|
|
361
|
-
console.log(color('yellow', `ā ļø Only ${event.tools.length} tools available`));
|
|
362
|
-
}
|
|
363
|
-
} else if (event.type === 'assistant' && event.message) {
|
|
364
|
-
const content = event.message.content;
|
|
365
|
-
if (Array.isArray(content)) {
|
|
366
|
-
for (const block of content) {
|
|
367
|
-
if (block.type === 'text') {
|
|
368
|
-
// Always show conversational text immediately
|
|
369
|
-
process.stdout.write(block.text);
|
|
370
|
-
} else if (block.type === 'tool_use') {
|
|
371
|
-
// Track the tool call for later result matching
|
|
372
|
-
if (block.id) {
|
|
373
|
-
global.pendingToolCalls.set(block.id, block.name);
|
|
374
|
-
}
|
|
375
|
-
logToolCall(block.name, 'call', block);
|
|
376
|
-
if (VERBOSE_OUTPUT) {
|
|
377
|
-
console.log(color('cyan', `\nš§ Using tool: ${block.name}`));
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
} else if (event.type === 'user' && event.message) {
|
|
383
|
-
const content = event.message.content;
|
|
384
|
-
if (Array.isArray(content)) {
|
|
385
|
-
for (const block of content) {
|
|
386
|
-
if (block.type === 'tool_result' && block.tool_use_id) {
|
|
387
|
-
// Match the result with the original tool call
|
|
388
|
-
const toolUseId = block.tool_use_id;
|
|
389
|
-
const toolName = global.pendingToolCalls.get(toolUseId) || `unknown_tool_${toolUseId}`;
|
|
390
|
-
|
|
391
|
-
logToolCall(toolName, 'result', null, block);
|
|
392
|
-
|
|
393
|
-
// Remove from pending calls after matching
|
|
394
|
-
global.pendingToolCalls.delete(toolUseId);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
} else if (event.type === 'result') {
|
|
399
|
-
const usage = event.usage || {};
|
|
400
|
-
const cost = event.total_cost_usd || 0;
|
|
401
|
-
if (VERBOSE_OUTPUT) {
|
|
402
|
-
console.log(color('green', `\n\nā
Completed in ${event.duration_ms}ms`));
|
|
403
|
-
console.log(color('yellow', `š° Cost: $${cost.toFixed(4)}`));
|
|
404
|
-
if (usage.input_tokens) {
|
|
405
|
-
console.log(color('cyan', `š Tokens: ${usage.input_tokens} in, ${usage.output_tokens} out`));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function handleStreamingOutput(data) {
|
|
412
|
-
jsonBuffer += data.toString();
|
|
413
|
-
|
|
414
|
-
// Split by newlines but keep the last incomplete line in buffer
|
|
415
|
-
const lines = jsonBuffer.split('\n');
|
|
416
|
-
jsonBuffer = lines.pop() || ''; // Keep last line (might be incomplete)
|
|
417
|
-
|
|
418
|
-
for (const line of lines) {
|
|
419
|
-
if (!line.trim()) continue; // Skip empty lines
|
|
420
|
-
|
|
421
|
-
try {
|
|
422
|
-
const event = JSON.parse(line);
|
|
423
|
-
handleEvent(event);
|
|
424
|
-
} catch (jsonError) {
|
|
425
|
-
// Log JSON parsing errors for troubleshooting
|
|
426
|
-
console.log(color('red', `š JSON parse error: ${jsonError.message}`));
|
|
427
|
-
console.log(color('yellow', `š Problematic line (${line.length} chars): ${line.substring(0, 200)}${line.length > 200 ? '...' : ''}`));
|
|
428
|
-
console.log(color('cyan', `š Buffer state: ${jsonBuffer.length} chars in buffer`));
|
|
429
|
-
|
|
430
|
-
// If we can't parse, put the line back in buffer and try to recover
|
|
431
|
-
jsonBuffer = line + '\n' + jsonBuffer;
|
|
432
|
-
console.log(color('yellow', `š Attempting to recover - ${jsonBuffer.length} chars in buffer`));
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// Add error handling
|
|
438
|
-
proc.on('error', (error) => {
|
|
439
|
-
console.log(color('red', `š Debug: Process error: ${error.message}`));
|
|
440
|
-
reject(error);
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// Write modified prompt to stdin
|
|
444
|
-
proc.stdin.write(modifiedPrompt);
|
|
445
|
-
proc.stdin.end();
|
|
446
|
-
|
|
447
|
-
let stdoutOutput = '';
|
|
448
|
-
let stderrOutput = '';
|
|
449
|
-
let lastError = '';
|
|
450
|
-
|
|
451
|
-
proc.stdout.on('data', (data) => {
|
|
452
|
-
stdoutOutput += data.toString();
|
|
453
|
-
|
|
454
|
-
handleStreamingOutput(data);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
proc.stderr.on('data', (data) => {
|
|
458
|
-
stderrOutput += data.toString();
|
|
459
|
-
process.stderr.write(data);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
proc.on('close', (code) => {
|
|
463
|
-
const sessionEndTime = Date.now();
|
|
464
|
-
const totalTime = sessionEndTime - startTime;
|
|
465
|
-
if (VERBOSE_OUTPUT) console.log(color('cyan', `\nā±ļø Stage 4: Session completed in ${totalTime}ms`));
|
|
466
|
-
|
|
467
|
-
// Try to parse any remaining data in buffer
|
|
468
|
-
if (jsonBuffer.trim()) {
|
|
469
|
-
try {
|
|
470
|
-
const event = JSON.parse(jsonBuffer);
|
|
471
|
-
handleEvent(event);
|
|
472
|
-
} catch (error) {
|
|
473
|
-
console.log(color('yellow', `ā ļø Could not parse remaining buffer data: ${error.message}`));
|
|
474
|
-
console.log(color('yellow', `ā ļø Remaining buffer: ${jsonBuffer.substring(0, 100)}...`));
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Display recent tool calls and performance summary only if verbose
|
|
479
|
-
if (VERBOSE_OUTPUT) {
|
|
480
|
-
displayRecentToolCalls();
|
|
481
|
-
console.log(color('cyan', `\nš Performance Summary:`));
|
|
482
|
-
console.log(color('cyan', ` ⢠Sandbox creation: ${sandboxCreateTime}ms`));
|
|
483
|
-
console.log(color('cyan', ` ⢠Environment setup: ${envCreateTime}ms`));
|
|
484
|
-
console.log(color('cyan', ` ⢠Claude Code session: ${totalTime - sandboxCreateTime - envCreateTime}ms`));
|
|
485
|
-
console.log(color('cyan', ` ⢠Total time: ${totalTime}ms`));
|
|
486
|
-
|
|
487
|
-
// Log file information if enabled
|
|
488
|
-
if (ENABLE_FILE_LOGGING && global.logFileHandle) {
|
|
489
|
-
console.log(color('yellow', `š Tool calls logged to: ${global.logFileHandle}`));
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
cleanup();
|
|
494
|
-
resolve(true);
|
|
495
|
-
});
|
|
496
|
-
});
|
|
497
|
-
} catch (error) {
|
|
498
|
-
console.log(color('red', `\nā Claude Code failed: ${error.message}`));
|
|
499
|
-
cleanup();
|
|
500
|
-
return false;
|
|
501
|
-
}
|
|
502
|
-
}
|