aiexecode 1.0.126 → 1.0.127
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.
Potentially problematic release.
This version of aiexecode might be problematic. Click here for more details.
- package/index.js +134 -20
- package/package.json +1 -1
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +1 -1
- package/prompts/completion_judge.txt +4 -0
- package/prompts/orchestrator.txt +53 -0
- package/src/commands/bg.js +129 -0
- package/src/frontend/App.js +100 -1
- package/src/frontend/components/BackgroundProcessList.js +175 -0
- package/src/system/ai_request.js +0 -19
- package/src/system/background_process.js +317 -0
- package/src/system/code_executer.js +469 -57
- package/src/system/output_helper.js +52 -9
- package/src/system/session.js +73 -10
- package/src/util/exit_handler.js +8 -0
- /package/payload_viewer/out/_next/static/{Ciog50_gZfMGwKNqVaI0v → 42iEoi-1o5MxNIZ1SWSvV}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{Ciog50_gZfMGwKNqVaI0v → 42iEoi-1o5MxNIZ1SWSvV}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{Ciog50_gZfMGwKNqVaI0v → 42iEoi-1o5MxNIZ1SWSvV}/_ssgManifest.js +0 -0
|
@@ -7,12 +7,412 @@ import { installRequiredPackages } from '../ai_based/pip_package_installer.js';
|
|
|
7
7
|
import { write_file } from '../tools/code_editor.js';
|
|
8
8
|
import { CONFIG_DIR, ensureConfigDirectory } from '../util/config.js';
|
|
9
9
|
import { createDebugLogger } from '../util/debug_log.js';
|
|
10
|
+
import crypto from 'crypto';
|
|
10
11
|
|
|
11
12
|
const debugLog = createDebugLogger('code_executer.log', 'code_executer');
|
|
12
13
|
|
|
13
14
|
// 이 파일은 파이썬과 쉘 코드를 실행하고 필요한 패키지를 설치하는 역할을 담당합니다.
|
|
14
15
|
// Orchestrator가 run_python_code와 bash 도구로 생산한 스크립트를 실제 환경에서 실행할 때 이 모듈을 직접 호출합니다.
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* PersistentShell - 앱 구동 동안 쉘 세션을 유지하는 클래스
|
|
19
|
+
*
|
|
20
|
+
* 기존 방식: 매번 임시 .sh 파일 생성 → spawn → 종료 (상태 소실)
|
|
21
|
+
* 새로운 방식: 하나의 bash 세션을 유지하며 stdin으로 명령 전송 (상태 유지)
|
|
22
|
+
*/
|
|
23
|
+
class PersistentShell {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.shell = null;
|
|
26
|
+
this.isReady = false;
|
|
27
|
+
this.commandQueue = [];
|
|
28
|
+
this.currentCommand = null;
|
|
29
|
+
this.outputBuffer = '';
|
|
30
|
+
this.stderrBuffer = '';
|
|
31
|
+
this.shellPath = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 쉘 세션 시작
|
|
36
|
+
*/
|
|
37
|
+
async start() {
|
|
38
|
+
if (this.shell && this.isReady) {
|
|
39
|
+
debugLog('[PersistentShell] Shell already running');
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.shellPath = await whichCommand("bash") || await whichCommand("sh");
|
|
44
|
+
if (!this.shellPath) {
|
|
45
|
+
debugLog('[PersistentShell] ERROR: No shell found');
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
debugLog(`[PersistentShell] Starting shell: ${this.shellPath}`);
|
|
50
|
+
|
|
51
|
+
this.shell = spawn(this.shellPath, [], {
|
|
52
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
53
|
+
env: {
|
|
54
|
+
...process.env,
|
|
55
|
+
DEBIAN_FRONTEND: 'noninteractive',
|
|
56
|
+
CI: 'true',
|
|
57
|
+
BATCH: '1',
|
|
58
|
+
// PS1을 비워서 프롬프트가 출력에 섞이지 않도록
|
|
59
|
+
PS1: '',
|
|
60
|
+
PS2: '',
|
|
61
|
+
},
|
|
62
|
+
cwd: process.cwd(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.shell.stdout.on('data', (data) => {
|
|
66
|
+
this.outputBuffer += data.toString();
|
|
67
|
+
this._checkCommandComplete();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.shell.stderr.on('data', (data) => {
|
|
71
|
+
this.stderrBuffer += data.toString();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
this.shell.on('close', (code) => {
|
|
75
|
+
debugLog(`[PersistentShell] Shell closed with code ${code}`);
|
|
76
|
+
|
|
77
|
+
// _stopInternal에서 이미 shell을 null로 설정한 경우 중복 처리 방지
|
|
78
|
+
if (!this.shell) return;
|
|
79
|
+
|
|
80
|
+
this.isReady = false;
|
|
81
|
+
this.shell = null;
|
|
82
|
+
|
|
83
|
+
// 현재 진행 중인 명령이 있다면 에러로 처리
|
|
84
|
+
if (this.currentCommand) {
|
|
85
|
+
if (this.currentCommand.timeoutHandle) {
|
|
86
|
+
clearTimeout(this.currentCommand.timeoutHandle);
|
|
87
|
+
}
|
|
88
|
+
this.currentCommand.reject(new Error('Shell closed unexpectedly'));
|
|
89
|
+
this.currentCommand = null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 큐에 있는 명령들도 에러로 처리
|
|
93
|
+
while (this.commandQueue.length > 0) {
|
|
94
|
+
const queued = this.commandQueue.shift();
|
|
95
|
+
queued.reject(new Error('Shell closed unexpectedly'));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
this.shell.on('error', (err) => {
|
|
100
|
+
debugLog(`[PersistentShell] Shell error: ${err.message}`);
|
|
101
|
+
|
|
102
|
+
// _stopInternal에서 이미 shell을 null로 설정한 경우 중복 처리 방지
|
|
103
|
+
if (!this.shell) return;
|
|
104
|
+
|
|
105
|
+
this.isReady = false;
|
|
106
|
+
|
|
107
|
+
// 현재 진행 중인 명령이 있다면 에러로 처리
|
|
108
|
+
if (this.currentCommand) {
|
|
109
|
+
if (this.currentCommand.timeoutHandle) {
|
|
110
|
+
clearTimeout(this.currentCommand.timeoutHandle);
|
|
111
|
+
}
|
|
112
|
+
this.currentCommand.reject(err);
|
|
113
|
+
this.currentCommand = null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 큐에 있는 명령들도 에러로 처리
|
|
117
|
+
while (this.commandQueue.length > 0) {
|
|
118
|
+
const queued = this.commandQueue.shift();
|
|
119
|
+
queued.reject(err);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.isReady = true;
|
|
124
|
+
debugLog('[PersistentShell] Shell started successfully');
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 명령어 완료 여부 확인 (마커 기반)
|
|
130
|
+
*/
|
|
131
|
+
_checkCommandComplete() {
|
|
132
|
+
if (!this.currentCommand) return;
|
|
133
|
+
|
|
134
|
+
const { endMarker, exitCodeMarker } = this.currentCommand;
|
|
135
|
+
|
|
136
|
+
// 종료 마커와 exit code 마커가 모두 있는지 확인
|
|
137
|
+
const endMarkerIndex = this.outputBuffer.indexOf(endMarker);
|
|
138
|
+
if (endMarkerIndex === -1) return;
|
|
139
|
+
|
|
140
|
+
const exitCodeMatch = this.outputBuffer.match(new RegExp(`${exitCodeMarker}(\\d+)${exitCodeMarker}`));
|
|
141
|
+
if (!exitCodeMatch) return;
|
|
142
|
+
|
|
143
|
+
// 명령어 출력 추출 (시작 마커 이후, exit code 마커 이전)
|
|
144
|
+
const { startMarker } = this.currentCommand;
|
|
145
|
+
const startMarkerIndex = this.outputBuffer.indexOf(startMarker);
|
|
146
|
+
const exitCodeMarkerIndex = this.outputBuffer.indexOf(exitCodeMarker);
|
|
147
|
+
|
|
148
|
+
let stdout = '';
|
|
149
|
+
if (startMarkerIndex !== -1 && exitCodeMarkerIndex !== -1) {
|
|
150
|
+
stdout = this.outputBuffer.substring(
|
|
151
|
+
startMarkerIndex + startMarker.length + 1, // +1 for newline
|
|
152
|
+
exitCodeMarkerIndex
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const exitCode = parseInt(exitCodeMatch[1], 10);
|
|
157
|
+
const stderr = this.stderrBuffer;
|
|
158
|
+
|
|
159
|
+
debugLog(`[PersistentShell] Command complete, exit code: ${exitCode}`);
|
|
160
|
+
debugLog(`[PersistentShell] stdout length: ${stdout.length}, stderr length: ${stderr.length}`);
|
|
161
|
+
|
|
162
|
+
// 타임아웃 핸들러 정리
|
|
163
|
+
if (this.currentCommand.timeoutHandle) {
|
|
164
|
+
clearTimeout(this.currentCommand.timeoutHandle);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 버퍼 초기화
|
|
168
|
+
this.outputBuffer = '';
|
|
169
|
+
this.stderrBuffer = '';
|
|
170
|
+
|
|
171
|
+
// Promise resolve
|
|
172
|
+
const { resolve } = this.currentCommand;
|
|
173
|
+
this.currentCommand = null;
|
|
174
|
+
|
|
175
|
+
resolve({
|
|
176
|
+
stdout: stdout.trim(),
|
|
177
|
+
stderr: stderr.trim(),
|
|
178
|
+
code: exitCode,
|
|
179
|
+
timeout: false
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// 다음 명령 처리
|
|
183
|
+
this._processNextCommand();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 큐에서 다음 명령 처리
|
|
188
|
+
*/
|
|
189
|
+
_processNextCommand() {
|
|
190
|
+
if (this.currentCommand || this.commandQueue.length === 0) return;
|
|
191
|
+
|
|
192
|
+
const next = this.commandQueue.shift();
|
|
193
|
+
this._executeCommand(next.script, next.timeoutMs, next.resolve, next.reject);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 명령어 실행 (내부)
|
|
198
|
+
*/
|
|
199
|
+
_executeCommand(script, timeoutMs, resolve, reject) {
|
|
200
|
+
// 고유한 마커 생성
|
|
201
|
+
const commandId = crypto.randomBytes(8).toString('hex');
|
|
202
|
+
const startMarker = `__AIEXE_START_${commandId}__`;
|
|
203
|
+
const endMarker = `__AIEXE_END_${commandId}__`;
|
|
204
|
+
const exitCodeMarker = `__AIEXE_EXIT_${commandId}_`;
|
|
205
|
+
|
|
206
|
+
this.currentCommand = {
|
|
207
|
+
script,
|
|
208
|
+
startMarker,
|
|
209
|
+
endMarker,
|
|
210
|
+
exitCodeMarker,
|
|
211
|
+
resolve,
|
|
212
|
+
reject,
|
|
213
|
+
startTime: Date.now()
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// 타임아웃 설정
|
|
217
|
+
const timeoutHandle = setTimeout(() => {
|
|
218
|
+
if (this.currentCommand && this.currentCommand.startMarker === startMarker) {
|
|
219
|
+
debugLog(`[PersistentShell] Command timeout after ${timeoutMs}ms`);
|
|
220
|
+
const stdout = this.outputBuffer;
|
|
221
|
+
const stderr = this.stderrBuffer + '\n[TIMEOUT] Command execution terminated due to timeout';
|
|
222
|
+
|
|
223
|
+
this.outputBuffer = '';
|
|
224
|
+
this.stderrBuffer = '';
|
|
225
|
+
this.currentCommand = null;
|
|
226
|
+
|
|
227
|
+
resolve({
|
|
228
|
+
stdout: stdout.trim(),
|
|
229
|
+
stderr: stderr.trim(),
|
|
230
|
+
code: 1,
|
|
231
|
+
timeout: true
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// 타임아웃 후 쉘 재시작 (상태가 불확실하므로)
|
|
235
|
+
this.restart();
|
|
236
|
+
}
|
|
237
|
+
}, timeoutMs);
|
|
238
|
+
|
|
239
|
+
this.currentCommand.timeoutHandle = timeoutHandle;
|
|
240
|
+
|
|
241
|
+
// 명령어를 마커로 감싸서 전송
|
|
242
|
+
// 형식: echo start_marker; script; exit_code=$?; echo exit_marker$exit_code_exit_marker; echo end_marker
|
|
243
|
+
const wrappedScript = `echo '${startMarker}'
|
|
244
|
+
${script}
|
|
245
|
+
__exit_code__=$?
|
|
246
|
+
echo '${exitCodeMarker}'$__exit_code__'${exitCodeMarker}'
|
|
247
|
+
echo '${endMarker}'
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
debugLog(`[PersistentShell] Executing command (id: ${commandId})`);
|
|
251
|
+
debugLog(`[PersistentShell] Script: ${script.substring(0, 100)}${script.length > 100 ? '...' : ''}`);
|
|
252
|
+
|
|
253
|
+
this.shell.stdin.write(wrappedScript);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 명령어 실행 (외부 인터페이스)
|
|
258
|
+
*/
|
|
259
|
+
async exec(script, timeoutMs = 1200000) {
|
|
260
|
+
if (!this.isReady) {
|
|
261
|
+
const started = await this.start();
|
|
262
|
+
if (!started) {
|
|
263
|
+
return {
|
|
264
|
+
stdout: '',
|
|
265
|
+
stderr: 'Failed to start shell',
|
|
266
|
+
code: 1,
|
|
267
|
+
timeout: false
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return new Promise((resolve, reject) => {
|
|
273
|
+
if (this.currentCommand) {
|
|
274
|
+
// 이미 실행 중인 명령이 있으면 큐에 추가
|
|
275
|
+
this.commandQueue.push({ script, timeoutMs, resolve, reject });
|
|
276
|
+
} else {
|
|
277
|
+
this._executeCommand(script, timeoutMs, resolve, reject);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 쉘 재시작 (큐 보존)
|
|
284
|
+
*/
|
|
285
|
+
async restart() {
|
|
286
|
+
debugLog('[PersistentShell] Restarting shell...');
|
|
287
|
+
|
|
288
|
+
// 큐 보존
|
|
289
|
+
const savedQueue = [...this.commandQueue];
|
|
290
|
+
this.commandQueue = [];
|
|
291
|
+
|
|
292
|
+
await this._stopInternal(false); // 큐 에러 처리 안 함
|
|
293
|
+
const started = await this.start();
|
|
294
|
+
|
|
295
|
+
// 큐 복원 및 처리 재개
|
|
296
|
+
if (started && savedQueue.length > 0) {
|
|
297
|
+
debugLog(`[PersistentShell] Restoring ${savedQueue.length} queued commands`);
|
|
298
|
+
this.commandQueue = savedQueue;
|
|
299
|
+
this._processNextCommand();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return started;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 쉘 종료 (외부 인터페이스)
|
|
307
|
+
*/
|
|
308
|
+
async stop() {
|
|
309
|
+
await this._stopInternal(true);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 쉘 종료 (내부)
|
|
314
|
+
* @param {boolean} rejectQueue - 큐에 있는 명령들을 에러로 처리할지 여부
|
|
315
|
+
*/
|
|
316
|
+
async _stopInternal(rejectQueue = true) {
|
|
317
|
+
if (this.shell) {
|
|
318
|
+
debugLog('[PersistentShell] Stopping shell...');
|
|
319
|
+
|
|
320
|
+
// 현재 명령의 타임아웃 핸들러 정리
|
|
321
|
+
if (this.currentCommand && this.currentCommand.timeoutHandle) {
|
|
322
|
+
clearTimeout(this.currentCommand.timeoutHandle);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// close 이벤트에서 큐가 처리되지 않도록 플래그 설정
|
|
326
|
+
const shellToKill = this.shell;
|
|
327
|
+
this.shell = null;
|
|
328
|
+
this.isReady = false;
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
shellToKill.stdin.end();
|
|
332
|
+
shellToKill.kill('SIGTERM');
|
|
333
|
+
|
|
334
|
+
// 잠시 대기 후 강제 종료
|
|
335
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
336
|
+
if (!shellToKill.killed) {
|
|
337
|
+
shellToKill.kill('SIGKILL');
|
|
338
|
+
}
|
|
339
|
+
} catch (err) {
|
|
340
|
+
debugLog(`[PersistentShell] Error stopping shell: ${err.message}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// 큐 처리
|
|
344
|
+
if (rejectQueue) {
|
|
345
|
+
while (this.commandQueue.length > 0) {
|
|
346
|
+
const queued = this.commandQueue.shift();
|
|
347
|
+
queued.reject(new Error('Shell stopped'));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
this.currentCommand = null;
|
|
352
|
+
this.outputBuffer = '';
|
|
353
|
+
this.stderrBuffer = '';
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 현재 작업 디렉토리 확인
|
|
359
|
+
*/
|
|
360
|
+
async getCwd() {
|
|
361
|
+
const result = await this.exec('pwd', 5000);
|
|
362
|
+
return result.code === 0 ? result.stdout.trim() : null;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* 작업 디렉토리를 지정된 경로로 리셋
|
|
367
|
+
* @param {string} targetDir - 이동할 디렉토리 (기본: process.cwd())
|
|
368
|
+
*/
|
|
369
|
+
async resetCwd(targetDir = null) {
|
|
370
|
+
const dir = targetDir || process.cwd();
|
|
371
|
+
debugLog(`[PersistentShell] Resetting cwd to: ${dir}`);
|
|
372
|
+
const result = await this.exec(`cd "${dir}"`, 5000);
|
|
373
|
+
if (result.code !== 0) {
|
|
374
|
+
debugLog(`[PersistentShell] Failed to reset cwd: ${result.stderr}`);
|
|
375
|
+
}
|
|
376
|
+
return result.code === 0;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// 싱글톤 인스턴스
|
|
381
|
+
let persistentShellInstance = null;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* PersistentShell 싱글톤 인스턴스 가져오기
|
|
385
|
+
*/
|
|
386
|
+
export function getPersistentShell() {
|
|
387
|
+
if (!persistentShellInstance) {
|
|
388
|
+
persistentShellInstance = new PersistentShell();
|
|
389
|
+
}
|
|
390
|
+
return persistentShellInstance;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* PersistentShell 종료 (앱 종료 시 호출)
|
|
395
|
+
*/
|
|
396
|
+
export async function closePersistentShell() {
|
|
397
|
+
if (persistentShellInstance) {
|
|
398
|
+
await persistentShellInstance.stop();
|
|
399
|
+
persistentShellInstance = null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* 쉘의 작업 디렉토리를 리셋 (새 세션 시작 시 호출)
|
|
405
|
+
* @param {string} targetDir - 이동할 디렉토리 (기본: process.cwd())
|
|
406
|
+
*/
|
|
407
|
+
export async function resetShellCwd(targetDir = null) {
|
|
408
|
+
const shell = getPersistentShell();
|
|
409
|
+
if (shell.isReady) {
|
|
410
|
+
return await shell.resetCwd(targetDir);
|
|
411
|
+
}
|
|
412
|
+
// 쉘이 아직 시작되지 않았으면 start()에서 cwd가 설정되므로 OK
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
|
|
16
416
|
dotenv.config({ quiet: true });
|
|
17
417
|
|
|
18
418
|
// 사용 가능한 파이썬 실행 파일 경로를 찾아 반환합니다.
|
|
@@ -322,8 +722,8 @@ export async function execPythonCode(python_code, args = [], timeoutMs = 1200000
|
|
|
322
722
|
}
|
|
323
723
|
}
|
|
324
724
|
|
|
325
|
-
// 쉘 스크립트를
|
|
326
|
-
//
|
|
725
|
+
// 쉘 스크립트를 PersistentShell을 통해 실행합니다.
|
|
726
|
+
// 쉘 세션이 유지되므로 cd, 환경변수 등의 상태가 보존됩니다.
|
|
327
727
|
export async function execShellScript(script, timeoutMs = 1200000) {
|
|
328
728
|
debugLog('========================================');
|
|
329
729
|
debugLog('====== execShellScript START ===========');
|
|
@@ -332,99 +732,96 @@ export async function execShellScript(script, timeoutMs = 1200000) {
|
|
|
332
732
|
debugLog(`[execShellScript] Script preview (first 200 chars): ${script.substring(0, 200)}${script.length > 200 ? '...' : ''}`);
|
|
333
733
|
debugLog(`[execShellScript] Timeout: ${timeoutMs}ms`);
|
|
334
734
|
|
|
335
|
-
|
|
336
|
-
|
|
735
|
+
const shell = getPersistentShell();
|
|
736
|
+
|
|
737
|
+
try {
|
|
738
|
+
const result = await shell.exec(script, timeoutMs);
|
|
739
|
+
|
|
740
|
+
debugLog(`[execShellScript] Execution complete`);
|
|
741
|
+
debugLog(`[execShellScript] Exit code: ${result.code}`);
|
|
742
|
+
debugLog(`[execShellScript] Stdout length: ${result.stdout.length} bytes`);
|
|
743
|
+
debugLog(`[execShellScript] Stderr length: ${result.stderr.length} bytes`);
|
|
744
|
+
debugLog(`[execShellScript] Timed out: ${result.timeout}`);
|
|
745
|
+
if (result.stdout) {
|
|
746
|
+
debugLog(`[execShellScript] Stdout preview: ${result.stdout.substring(0, 500)}${result.stdout.length > 500 ? '...' : ''}`);
|
|
747
|
+
}
|
|
748
|
+
if (result.stderr) {
|
|
749
|
+
debugLog(`[execShellScript] Stderr content: ${result.stderr}`);
|
|
750
|
+
}
|
|
337
751
|
|
|
338
|
-
debugLog(`[execShellScript] Finding shell executable...`);
|
|
339
|
-
let shellPath = await whichCommand("bash") || await whichCommand("sh");
|
|
340
|
-
if (!shellPath) {
|
|
341
|
-
debugLog(`[execShellScript] ERROR: No shell found (bash/sh)`);
|
|
342
752
|
debugLog('========================================');
|
|
343
|
-
debugLog('====== execShellScript END
|
|
753
|
+
debugLog('====== execShellScript END =============');
|
|
344
754
|
debugLog('========================================');
|
|
755
|
+
return result;
|
|
756
|
+
|
|
757
|
+
} catch (err) {
|
|
758
|
+
debugLog(`[execShellScript] EXCEPTION: ${err.message}`);
|
|
759
|
+
debugLog(`[execShellScript] Exception stack: ${err.stack}`);
|
|
760
|
+
debugLog('========================================');
|
|
761
|
+
debugLog('====== execShellScript END (ERROR) =====');
|
|
762
|
+
debugLog('========================================');
|
|
763
|
+
throw err;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// 기존 방식: 임시 파일로 쉘 스크립트 실행 (fallback용)
|
|
768
|
+
export async function execShellScriptLegacy(script, timeoutMs = 1200000) {
|
|
769
|
+
debugLog('========================================');
|
|
770
|
+
debugLog('====== execShellScriptLegacy START =====');
|
|
771
|
+
debugLog('========================================');
|
|
772
|
+
debugLog(`[execShellScriptLegacy] Script length: ${script.length} characters`);
|
|
773
|
+
|
|
774
|
+
let shellPath = await whichCommand("bash") || await whichCommand("sh");
|
|
775
|
+
if (!shellPath) {
|
|
776
|
+
debugLog(`[execShellScriptLegacy] ERROR: No shell found (bash/sh)`);
|
|
345
777
|
return null;
|
|
346
778
|
}
|
|
347
|
-
debugLog(`[execShellScript] Using shell: ${shellPath}`);
|
|
348
779
|
|
|
349
780
|
const tempFile = join(getTempDirPath(), `temp_${Date.now()}.sh`);
|
|
350
|
-
debugLog(`[execShellScript] Temp file path: ${tempFile}`);
|
|
351
781
|
|
|
352
782
|
try {
|
|
353
|
-
// 기존 임시 파일이 있다면 삭제
|
|
354
783
|
try {
|
|
355
784
|
await safeAccess(tempFile);
|
|
356
|
-
debugLog(`[execShellScript] Temp file already exists, deleting...`);
|
|
357
785
|
await safeUnlink(tempFile);
|
|
358
786
|
} catch (err) {
|
|
359
|
-
debugLog(`[execShellScript] Temp file does not exist (normal)`);
|
|
360
787
|
// 파일이 없으면 무시
|
|
361
788
|
}
|
|
362
789
|
|
|
363
|
-
debugLog(`[execShellScript] Writing script to temp file...`);
|
|
364
790
|
const result = await write_file({ file_path: tempFile, content: script });
|
|
365
791
|
if (!result.operation_successful) {
|
|
366
|
-
debugLog(`[execShellScript] ERROR: Failed to create temp file - ${result.error}`);
|
|
367
792
|
throw new Error(`Failed to create temp file: ${result.error}`);
|
|
368
793
|
}
|
|
369
|
-
debugLog(`[execShellScript] Script written successfully`);
|
|
370
794
|
} catch (err) {
|
|
371
|
-
debugLog(`[
|
|
372
|
-
debugLog('========================================');
|
|
373
|
-
debugLog('====== execShellScript END (WRITE ERR) =');
|
|
374
|
-
debugLog('========================================');
|
|
795
|
+
debugLog(`[execShellScriptLegacy] EXCEPTION during file write: ${err.message}`);
|
|
375
796
|
throw err;
|
|
376
797
|
}
|
|
377
798
|
|
|
378
799
|
try {
|
|
379
|
-
debugLog(`[execShellScript] Spawning shell process...`);
|
|
380
|
-
debugLog(`[execShellScript] Command: ${shellPath} ${tempFile}`);
|
|
381
|
-
|
|
382
800
|
const shell = spawn(shellPath, [tempFile], {
|
|
383
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
801
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
384
802
|
env: {
|
|
385
803
|
...process.env,
|
|
386
|
-
DEBIAN_FRONTEND: 'noninteractive',
|
|
387
|
-
CI: 'true',
|
|
388
|
-
BATCH: '1'
|
|
804
|
+
DEBIAN_FRONTEND: 'noninteractive',
|
|
805
|
+
CI: 'true',
|
|
806
|
+
BATCH: '1'
|
|
389
807
|
},
|
|
390
|
-
detached: true
|
|
808
|
+
detached: true
|
|
391
809
|
});
|
|
392
810
|
|
|
393
|
-
debugLog(`[execShellScript] Process spawned - PID: ${shell.pid}`);
|
|
394
|
-
debugLog(`[execShellScript] Waiting for process to complete...`);
|
|
395
|
-
|
|
396
811
|
const result = await createProcessHandler(shell, tempFile, timeoutMs);
|
|
397
812
|
|
|
398
|
-
debugLog(`[execShellScript] Execution complete`);
|
|
399
|
-
debugLog(`[execShellScript] Exit code: ${result.code}`);
|
|
400
|
-
debugLog(`[execShellScript] Stdout length: ${result.stdout.length} bytes`);
|
|
401
|
-
debugLog(`[execShellScript] Stderr length: ${result.stderr.length} bytes`);
|
|
402
|
-
debugLog(`[execShellScript] Timed out: ${result.timeout}`);
|
|
403
|
-
if (result.stdout) {
|
|
404
|
-
debugLog(`[execShellScript] Stdout preview: ${result.stdout.substring(0, 500)}${result.stdout.length > 500 ? '...' : ''}`);
|
|
405
|
-
}
|
|
406
|
-
if (result.stderr) {
|
|
407
|
-
debugLog(`[execShellScript] Stderr content: ${result.stderr}`);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
813
|
debugLog('========================================');
|
|
411
|
-
debugLog('======
|
|
814
|
+
debugLog('====== execShellScriptLegacy END =======');
|
|
412
815
|
debugLog('========================================');
|
|
413
816
|
return result;
|
|
414
817
|
|
|
415
818
|
} catch (err) {
|
|
416
|
-
debugLog(`[
|
|
417
|
-
debugLog(`[execShellScript] Exception stack: ${err.stack}`);
|
|
819
|
+
debugLog(`[execShellScriptLegacy] EXCEPTION: ${err.message}`);
|
|
418
820
|
try {
|
|
419
821
|
await safeUnlink(tempFile);
|
|
420
|
-
debugLog(`[execShellScript] Cleaned up temp file after error`);
|
|
421
822
|
} catch (unlinkErr) {
|
|
422
|
-
|
|
823
|
+
// ignore
|
|
423
824
|
}
|
|
424
|
-
|
|
425
|
-
debugLog('========================================');
|
|
426
|
-
debugLog('====== execShellScript END (ERROR) =====');
|
|
427
|
-
debugLog('========================================');
|
|
428
825
|
throw err;
|
|
429
826
|
}
|
|
430
827
|
}
|
|
@@ -581,7 +978,13 @@ export const runPythonCodeSchema = {
|
|
|
581
978
|
|
|
582
979
|
export const bashSchema = {
|
|
583
980
|
name: "bash",
|
|
584
|
-
description: `Executes bash shell commands
|
|
981
|
+
description: `Executes bash shell commands in a persistent shell session.
|
|
982
|
+
|
|
983
|
+
SHELL STATE PERSISTENCE:
|
|
984
|
+
- Working directory (cd) is preserved across calls
|
|
985
|
+
- Environment variables set with export persist
|
|
986
|
+
- Shell history and state are maintained throughout the session
|
|
987
|
+
- You can cd to a directory in one call and use relative paths in the next
|
|
585
988
|
|
|
586
989
|
IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file reading/searching operations - use specialized tools instead.
|
|
587
990
|
|
|
@@ -609,19 +1012,28 @@ Best Practices:
|
|
|
609
1012
|
- Avoid interactive commands that require user input
|
|
610
1013
|
- For calculations, use non-interactive bc or Python (never mental arithmetic)
|
|
611
1014
|
|
|
612
|
-
This tool handles: file system operations (mkdir, rm, mv, cp, chmod), git commands, package management (npm, pip), and CLI tools
|
|
1015
|
+
This tool handles: file system operations (mkdir, rm, mv, cp, chmod), git commands, package management (npm, pip), and CLI tools.
|
|
1016
|
+
|
|
1017
|
+
BACKGROUND EXECUTION:
|
|
1018
|
+
- Set background: true to run long-running commands without blocking
|
|
1019
|
+
- Background processes can be monitored and killed via the UI
|
|
1020
|
+
- Use for: servers, watchers, long builds, or any command that doesn't need immediate output`,
|
|
613
1021
|
parameters: {
|
|
614
1022
|
type: "object",
|
|
615
1023
|
required: ["script"],
|
|
616
1024
|
properties: {
|
|
617
1025
|
script: {
|
|
618
1026
|
type: "string",
|
|
619
|
-
description: "Bash script to execute
|
|
1027
|
+
description: "Bash script to execute in the persistent shell. Working directory and environment variables persist across calls."
|
|
1028
|
+
},
|
|
1029
|
+
background: {
|
|
1030
|
+
type: "boolean",
|
|
1031
|
+
description: "If true, run the command in the background. The command will continue running and can be monitored/killed later. Use for long-running processes like servers or watchers."
|
|
620
1032
|
}
|
|
621
1033
|
},
|
|
622
1034
|
additionalProperties: false
|
|
623
1035
|
},
|
|
624
|
-
strict:
|
|
1036
|
+
strict: false,
|
|
625
1037
|
ui_display: {
|
|
626
1038
|
show_tool_call: true,
|
|
627
1039
|
show_tool_result: true,
|