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