sandboxbox 3.0.15 → 3.0.16
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/utils/commands/claude.js +120 -42
- package/utils/sandbox.js +9 -0
package/package.json
CHANGED
package/utils/commands/claude.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
1
|
+
import { existsSync, writeFileSync, appendFileSync } from 'fs';
|
|
2
2
|
import { resolve, join } from 'path';
|
|
3
3
|
import { spawn, execSync } from 'child_process';
|
|
4
4
|
import { color } from '../colors.js';
|
|
@@ -17,6 +17,50 @@ const ALLOWED_TOOLS = [
|
|
|
17
17
|
'mcp__vexify__search_code'
|
|
18
18
|
];
|
|
19
19
|
|
|
20
|
+
// Console output configuration
|
|
21
|
+
const MAX_CONSOLE_LINES = parseInt(process.env.SANDBOX_MAX_CONSOLE_LINES) || 5;
|
|
22
|
+
const ENABLE_FILE_LOGGING = process.env.SANDBOX_ENABLE_FILE_LOGGING === 'true';
|
|
23
|
+
global.toolCallLog = [];
|
|
24
|
+
global.logFileHandle = null;
|
|
25
|
+
|
|
26
|
+
// Helper function to log tool calls
|
|
27
|
+
function logToolCall(toolName, action = 'call') {
|
|
28
|
+
const timestamp = new Date().toISOString();
|
|
29
|
+
const logEntry = `[${timestamp}] Tool ${action}: ${toolName}`;
|
|
30
|
+
|
|
31
|
+
global.toolCallLog.push(logEntry);
|
|
32
|
+
|
|
33
|
+
// Keep only the last MAX_CONSOLE_LINES for console display
|
|
34
|
+
if (global.toolCallLog.length > MAX_CONSOLE_LINES) {
|
|
35
|
+
global.toolCallLog = global.toolCallLog.slice(-MAX_CONSOLE_LINES);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Optionally log to file
|
|
39
|
+
if (ENABLE_FILE_LOGGING) {
|
|
40
|
+
try {
|
|
41
|
+
if (!global.logFileHandle) {
|
|
42
|
+
const logFileName = `sandboxbox-tool-calls-${Date.now()}.log`;
|
|
43
|
+
writeFileSync(logFileName, `# SandboxBox Tool Calls Log\n# Started: ${timestamp}\n\n`);
|
|
44
|
+
global.logFileHandle = logFileName;
|
|
45
|
+
}
|
|
46
|
+
appendFileSync(global.logFileHandle, logEntry + '\n');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Don't fail if logging fails
|
|
49
|
+
console.log(color('yellow', `⚠️ Could not write to log file: ${error.message}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Helper function to display recent tool calls
|
|
55
|
+
function displayRecentToolCalls() {
|
|
56
|
+
if (global.toolCallLog.length > 0) {
|
|
57
|
+
console.log(color('cyan', `\n🔧 Recent tool calls (showing last ${global.toolCallLog.length}):`));
|
|
58
|
+
global.toolCallLog.forEach(log => {
|
|
59
|
+
console.log(color('cyan', ` ${log}`));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
20
64
|
|
|
21
65
|
export async function claudeCommand(projectDir, prompt) {
|
|
22
66
|
if (!existsSync(projectDir)) {
|
|
@@ -99,56 +143,71 @@ export async function claudeCommand(projectDir, prompt) {
|
|
|
99
143
|
});
|
|
100
144
|
|
|
101
145
|
let claudeStarted = false;
|
|
146
|
+
let jsonBuffer = ''; // Buffer for incomplete JSON lines
|
|
147
|
+
|
|
148
|
+
function handleEvent(event) {
|
|
149
|
+
if (event.type === 'system' && event.subtype === 'init') {
|
|
150
|
+
if (!claudeStarted) {
|
|
151
|
+
const claudeCreateTime = Date.now() - claudeStartTime;
|
|
152
|
+
console.log(color('green', `✅ Claude Code started in ${claudeCreateTime}ms`));
|
|
153
|
+
claudeStarted = true;
|
|
154
|
+
}
|
|
155
|
+
console.log(color('green', `✅ Session started (${event.session_id.substring(0, 8)}...)`));
|
|
156
|
+
console.log(color('cyan', `📦 Model: ${event.model}`));
|
|
157
|
+
console.log(color('cyan', `🔧 Tools: ${event.tools.length} available`));
|
|
158
|
+
|
|
159
|
+
// List available tools
|
|
160
|
+
if (event.tools && event.tools.length > 0) {
|
|
161
|
+
const toolNames = event.tools.map(tool => tool.name || tool).sort();
|
|
162
|
+
console.log(color('yellow', ` Available: ${toolNames.join(', ')}\n`));
|
|
163
|
+
} else {
|
|
164
|
+
console.log('');
|
|
165
|
+
}
|
|
166
|
+
} else if (event.type === 'assistant' && event.message) {
|
|
167
|
+
const content = event.message.content;
|
|
168
|
+
if (Array.isArray(content)) {
|
|
169
|
+
for (const block of content) {
|
|
170
|
+
if (block.type === 'text') {
|
|
171
|
+
process.stdout.write(block.text);
|
|
172
|
+
} else if (block.type === 'tool_use') {
|
|
173
|
+
logToolCall(block.name);
|
|
174
|
+
console.log(color('cyan', `\n🔧 Using tool: ${block.name}`));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else if (event.type === 'result') {
|
|
179
|
+
const usage = event.usage || {};
|
|
180
|
+
const cost = event.total_cost_usd || 0;
|
|
181
|
+
console.log(color('green', `\n\n✅ Completed in ${event.duration_ms}ms`));
|
|
182
|
+
console.log(color('yellow', `💰 Cost: $${cost.toFixed(4)}`));
|
|
183
|
+
if (usage.input_tokens) {
|
|
184
|
+
console.log(color('cyan', `📊 Tokens: ${usage.input_tokens} in, ${usage.output_tokens} out`));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
102
188
|
|
|
103
189
|
function handleStreamingOutput(data) {
|
|
104
|
-
|
|
190
|
+
jsonBuffer += data.toString();
|
|
191
|
+
|
|
192
|
+
// Split by newlines but keep the last incomplete line in buffer
|
|
193
|
+
const lines = jsonBuffer.split('\n');
|
|
194
|
+
jsonBuffer = lines.pop() || ''; // Keep last line (might be incomplete)
|
|
105
195
|
|
|
106
196
|
for (const line of lines) {
|
|
197
|
+
if (!line.trim()) continue; // Skip empty lines
|
|
198
|
+
|
|
107
199
|
try {
|
|
108
200
|
const event = JSON.parse(line);
|
|
109
|
-
|
|
110
|
-
if (event.type === 'system' && event.subtype === 'init') {
|
|
111
|
-
if (!claudeStarted) {
|
|
112
|
-
const claudeCreateTime = Date.now() - claudeStartTime;
|
|
113
|
-
console.log(color('green', `✅ Claude Code started in ${claudeCreateTime}ms`));
|
|
114
|
-
claudeStarted = true;
|
|
115
|
-
}
|
|
116
|
-
console.log(color('green', `✅ Session started (${event.session_id.substring(0, 8)}...)`));
|
|
117
|
-
console.log(color('cyan', `📦 Model: ${event.model}`));
|
|
118
|
-
console.log(color('cyan', `🔧 Tools: ${event.tools.length} available`));
|
|
119
|
-
|
|
120
|
-
// List available tools
|
|
121
|
-
if (event.tools && event.tools.length > 0) {
|
|
122
|
-
const toolNames = event.tools.map(tool => tool.name || tool).sort();
|
|
123
|
-
console.log(color('yellow', ` Available: ${toolNames.join(', ')}\n`));
|
|
124
|
-
} else {
|
|
125
|
-
console.log('');
|
|
126
|
-
}
|
|
127
|
-
} else if (event.type === 'assistant' && event.message) {
|
|
128
|
-
const content = event.message.content;
|
|
129
|
-
if (Array.isArray(content)) {
|
|
130
|
-
for (const block of content) {
|
|
131
|
-
if (block.type === 'text') {
|
|
132
|
-
process.stdout.write(block.text);
|
|
133
|
-
} else if (block.type === 'tool_use') {
|
|
134
|
-
console.log(color('cyan', `\n🔧 Using tool: ${block.name}`));
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} else if (event.type === 'result') {
|
|
139
|
-
const usage = event.usage || {};
|
|
140
|
-
const cost = event.total_cost_usd || 0;
|
|
141
|
-
console.log(color('green', `\n\n✅ Completed in ${event.duration_ms}ms`));
|
|
142
|
-
console.log(color('yellow', `💰 Cost: $${cost.toFixed(4)}`));
|
|
143
|
-
if (usage.input_tokens) {
|
|
144
|
-
console.log(color('cyan', `📊 Tokens: ${usage.input_tokens} in, ${usage.output_tokens} out`));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
201
|
+
handleEvent(event);
|
|
147
202
|
} catch (jsonError) {
|
|
148
203
|
// Log JSON parsing errors for troubleshooting
|
|
149
204
|
console.log(color('red', `🔍 JSON parse error: ${jsonError.message}`));
|
|
150
|
-
console.log(color('yellow', `🔍 Problematic line: ${line}`));
|
|
151
|
-
console.log(color('cyan',
|
|
205
|
+
console.log(color('yellow', `🔍 Problematic line (${line.length} chars): ${line.substring(0, 200)}${line.length > 200 ? '...' : ''}`));
|
|
206
|
+
console.log(color('cyan', `🔍 Buffer state: ${jsonBuffer.length} chars in buffer`));
|
|
207
|
+
|
|
208
|
+
// If we can't parse, put the line back in buffer and try to recover
|
|
209
|
+
jsonBuffer = line + '\n' + jsonBuffer;
|
|
210
|
+
console.log(color('yellow', `🔍 Attempting to recover - ${jsonBuffer.length} chars in buffer`));
|
|
152
211
|
}
|
|
153
212
|
}
|
|
154
213
|
}
|
|
@@ -183,6 +242,20 @@ export async function claudeCommand(projectDir, prompt) {
|
|
|
183
242
|
const totalTime = sessionEndTime - startTime;
|
|
184
243
|
console.log(color('cyan', `\n⏱️ Stage 4: Session completed in ${totalTime}ms`));
|
|
185
244
|
|
|
245
|
+
// Try to parse any remaining data in buffer
|
|
246
|
+
if (jsonBuffer.trim()) {
|
|
247
|
+
try {
|
|
248
|
+
const event = JSON.parse(jsonBuffer);
|
|
249
|
+
handleEvent(event);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.log(color('yellow', `⚠️ Could not parse remaining buffer data: ${error.message}`));
|
|
252
|
+
console.log(color('yellow', `⚠️ Remaining buffer: ${jsonBuffer.substring(0, 100)}...`));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Display recent tool calls
|
|
257
|
+
displayRecentToolCalls();
|
|
258
|
+
|
|
186
259
|
// Performance summary
|
|
187
260
|
console.log(color('cyan', `\n📊 Performance Summary:`));
|
|
188
261
|
console.log(color('cyan', ` • Sandbox creation: ${sandboxCreateTime}ms`));
|
|
@@ -190,6 +263,11 @@ export async function claudeCommand(projectDir, prompt) {
|
|
|
190
263
|
console.log(color('cyan', ` • Claude Code session: ${totalTime - sandboxCreateTime - envCreateTime}ms`));
|
|
191
264
|
console.log(color('cyan', ` • Total time: ${totalTime}ms`));
|
|
192
265
|
|
|
266
|
+
// Log file information if enabled
|
|
267
|
+
if (ENABLE_FILE_LOGGING && global.logFileHandle) {
|
|
268
|
+
console.log(color('yellow', `📝 Tool calls logged to: ${global.logFileHandle}`));
|
|
269
|
+
}
|
|
270
|
+
|
|
193
271
|
cleanup();
|
|
194
272
|
resolve(true);
|
|
195
273
|
});
|
package/utils/sandbox.js
CHANGED
|
@@ -190,6 +190,15 @@ export function createSandbox(projectDir) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
const cleanup = () => {
|
|
193
|
+
// Close any log files that might be open
|
|
194
|
+
if (global.logFileHandle) {
|
|
195
|
+
try {
|
|
196
|
+
appendFileSync(global.logFileHandle, `\n# Session ended: ${new Date().toISOString()}\n`);
|
|
197
|
+
global.logFileHandle = null;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// Don't fail on log cleanup
|
|
200
|
+
}
|
|
201
|
+
}
|
|
193
202
|
rmSync(sandboxDir, { recursive: true, force: true });
|
|
194
203
|
};
|
|
195
204
|
|