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.

@@ -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
- // 파이썬 실행과 동일하게 Orchestrator의 지시에 따라 반복적으로 호출됩니다.
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
- // Intentional delay for testing pending state
336
- await new Promise(resolve => setTimeout(resolve, 13));
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 (NO SHELL) ==');
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(`[execShellScript] EXCEPTION during file write: ${err.message}`);
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'], // stdin을 ignore하여 입력 프롬프트 방지
810
+ stdio: ['ignore', 'pipe', 'pipe'],
384
811
  env: {
385
812
  ...process.env,
386
- DEBIAN_FRONTEND: 'noninteractive', // 비대화형 모드
387
- CI: 'true', // CI 환경으로 인식시켜 프롬프트 방지
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('====== execShellScript END =============');
823
+ debugLog('====== execShellScriptLegacy END =======');
412
824
  debugLog('========================================');
413
825
  return result;
414
826
 
415
827
  } catch (err) {
416
- debugLog(`[execShellScript] EXCEPTION: ${err.message}`);
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
- debugLog(`[execShellScript] Failed to delete temp file: ${unlinkErr.message}`);
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 with optional timeout.
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. Will be saved to a temporary file and run with bash. Keep scripts concise and output-limited to avoid truncation."
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: true,
1054
+ strict: false,
625
1055
  ui_display: {
626
1056
  show_tool_call: true,
627
1057
  show_tool_result: true,