aiexecode 1.0.94 → 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.
Files changed (80) hide show
  1. package/README.md +198 -88
  2. package/index.js +310 -86
  3. package/mcp-agent-lib/src/mcp_message_logger.js +17 -16
  4. package/package.json +4 -4
  5. package/payload_viewer/out/404/index.html +1 -1
  6. package/payload_viewer/out/404.html +1 -1
  7. package/payload_viewer/out/_next/static/chunks/{37d0cd2587a38f79.js → b6c0459f3789d25c.js} +1 -1
  8. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  9. package/payload_viewer/out/index.html +1 -1
  10. package/payload_viewer/out/index.txt +3 -3
  11. package/payload_viewer/web_server.js +361 -0
  12. package/prompts/completion_judge.txt +4 -0
  13. package/prompts/orchestrator.txt +116 -3
  14. package/src/LLMClient/client.js +401 -18
  15. package/src/LLMClient/converters/responses-to-claude.js +67 -18
  16. package/src/LLMClient/converters/responses-to-zai.js +667 -0
  17. package/src/LLMClient/errors.js +30 -4
  18. package/src/LLMClient/index.js +5 -0
  19. package/src/ai_based/completion_judge.js +263 -186
  20. package/src/ai_based/orchestrator.js +171 -35
  21. package/src/commands/agents.js +70 -0
  22. package/src/commands/apikey.js +1 -1
  23. package/src/commands/bg.js +129 -0
  24. package/src/commands/commands.js +51 -0
  25. package/src/commands/debug.js +52 -0
  26. package/src/commands/help.js +11 -1
  27. package/src/commands/model.js +42 -7
  28. package/src/commands/reasoning_effort.js +2 -2
  29. package/src/commands/skills.js +46 -0
  30. package/src/config/ai_models.js +106 -6
  31. package/src/config/constants.js +71 -0
  32. package/src/config/feature_flags.js +6 -7
  33. package/src/frontend/App.js +108 -1
  34. package/src/frontend/components/AutocompleteMenu.js +7 -1
  35. package/src/frontend/components/BackgroundProcessList.js +175 -0
  36. package/src/frontend/components/ConversationItem.js +26 -10
  37. package/src/frontend/components/CurrentModelView.js +2 -2
  38. package/src/frontend/components/HelpView.js +106 -2
  39. package/src/frontend/components/Input.js +33 -11
  40. package/src/frontend/components/ModelListView.js +1 -1
  41. package/src/frontend/components/SetupWizard.js +51 -8
  42. package/src/frontend/hooks/useFileCompletion.js +467 -0
  43. package/src/frontend/utils/toolUIFormatter.js +261 -0
  44. package/src/system/agents_loader.js +289 -0
  45. package/src/system/ai_request.js +156 -12
  46. package/src/system/background_process.js +317 -0
  47. package/src/system/code_executer.js +496 -56
  48. package/src/system/command_parser.js +33 -3
  49. package/src/system/conversation_state.js +265 -0
  50. package/src/system/conversation_trimmer.js +132 -0
  51. package/src/system/custom_command_loader.js +386 -0
  52. package/src/system/file_integrity.js +73 -10
  53. package/src/system/log.js +10 -2
  54. package/src/system/output_helper.js +52 -9
  55. package/src/system/session.js +213 -58
  56. package/src/system/session_memory.js +30 -2
  57. package/src/system/skill_loader.js +318 -0
  58. package/src/system/system_info.js +254 -40
  59. package/src/system/tool_approval.js +10 -0
  60. package/src/system/tool_registry.js +15 -1
  61. package/src/system/ui_events.js +11 -0
  62. package/src/tools/code_editor.js +16 -10
  63. package/src/tools/file_reader.js +66 -9
  64. package/src/tools/glob.js +0 -3
  65. package/src/tools/ripgrep.js +5 -7
  66. package/src/tools/skill_tool.js +122 -0
  67. package/src/tools/web_downloader.js +0 -3
  68. package/src/util/clone.js +174 -0
  69. package/src/util/config.js +55 -2
  70. package/src/util/config_migration.js +174 -0
  71. package/src/util/debug_log.js +8 -2
  72. package/src/util/exit_handler.js +8 -0
  73. package/src/util/file_reference_parser.js +132 -0
  74. package/src/util/path_validator.js +178 -0
  75. package/src/util/prompt_loader.js +91 -1
  76. package/src/util/safe_fs.js +66 -3
  77. package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
  78. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_buildManifest.js +0 -0
  79. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_clientMiddlewareManifest.json +0 -0
  80. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → 42iEoi-1o5MxNIZ1SWSvV}/_ssgManifest.js +0 -0
package/index.js CHANGED
@@ -13,6 +13,7 @@ import { loadPreviousSessions, reconstructUIHistory, deleteHistoryFile } from ".
13
13
  import { getModelForProvider } from "./src/system/ai_request.js";
14
14
  import { runSetupWizard, isConfigured } from "./src/util/setup_wizard.js";
15
15
  import { safeRm, safeMkdir, safeCopyFile, safeReaddir } from './src/util/safe_fs.js';
16
+ import { parseFileReferences } from './src/util/file_reference_parser.js';
16
17
  import chalk from 'chalk';
17
18
  import { startUI } from './src/frontend/index.js';
18
19
  import { uiEvents } from './src/system/ui_events.js';
@@ -21,9 +22,68 @@ import { Command } from 'commander';
21
22
  import { registerMcpCliCommands } from './src/cli/mcp_cli.js';
22
23
  import { createDebugLogger } from './src/util/debug_log.js';
23
24
  import { checkForUpdates } from './src/util/version_check.js';
24
-
25
25
  const debugLog = createDebugLogger('index.log', 'index');
26
26
 
27
+ /**
28
+ * 의존성 오류를 출력합니다.
29
+ */
30
+ function printDependencyError(check) {
31
+ const { osInfo, issues } = check;
32
+
33
+ console.log('');
34
+ console.log(chalk.bold.red('Missing Required Dependencies'));
35
+ console.log('');
36
+
37
+ // OS 정보
38
+ const osName = osInfo.name || check.os;
39
+ const osVersion = osInfo.version ? ` ${osInfo.version}` : '';
40
+ const pkgMgr = osInfo.packageManagerName ? ` (${osInfo.packageManagerName})` : '';
41
+ console.log(chalk.dim(`System: ${osName}${osVersion}${pkgMgr}`));
42
+ console.log('');
43
+
44
+ // 각 이슈 출력
45
+ issues.forEach((issue, idx) => {
46
+ if (issue.type === 'unsupported_os') {
47
+ console.log(chalk.red(`✗ ${issue.message}`));
48
+ console.log(chalk.dim(` ${issue.details}`));
49
+
50
+ if (issue.suggestions && issue.suggestions.length > 0) {
51
+ console.log('');
52
+ console.log(chalk.yellow('Alternatives:'));
53
+ issue.suggestions.forEach(suggestion => {
54
+ console.log(chalk.dim(` • ${suggestion}`));
55
+ });
56
+ }
57
+ } else if (issue.type === 'missing_command') {
58
+ console.log(chalk.red(`✗ ${issue.displayName || issue.command}`));
59
+ if (issue.description) {
60
+ console.log(chalk.dim(` ${issue.description}`));
61
+ }
62
+
63
+ console.log(chalk.green(' Install:'));
64
+ console.log(chalk.cyan(` $ ${issue.install.primary}`));
65
+
66
+ if (issue.install.alternatives && issue.install.alternatives.length > 0) {
67
+ issue.install.alternatives.forEach(alt => {
68
+ console.log(chalk.dim(` $ ${alt}`));
69
+ });
70
+ }
71
+
72
+ if (issue.install.url) {
73
+ console.log(chalk.dim(` More info: ${issue.install.url}`));
74
+ }
75
+ }
76
+
77
+ if (idx < issues.length - 1) {
78
+ console.log('');
79
+ }
80
+ });
81
+
82
+ console.log('');
83
+ console.log(chalk.yellow('After installing, restart your terminal and run aiexecode again.'));
84
+ console.log('');
85
+ }
86
+
27
87
  // Read version from package.json
28
88
  const __dirname = dirname(fileURLToPath(import.meta.url));
29
89
  const packageJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'));
@@ -37,8 +97,12 @@ program
37
97
  .version(VERSION)
38
98
  .option('-c, --continue <session_id>', 'Continue from previous session (16-char hex session ID)')
39
99
  .option('--viewer', 'Start payload viewer web server')
40
- .option('-p, --port <port>', 'Port for payload viewer (default: 3300)', '3300')
100
+ .option('--port <port>', 'Port for payload viewer (default: 3300)', '3300')
41
101
  .option('--init', 'Initialize project-specific prompts in current directory')
102
+ .option('--dangerously-skip-permissions', 'Skip all tool approval prompts (use with caution)')
103
+ .option('-p, --pipe', 'Pipe mode: non-interactive execution without REPL (includes --dangerously-skip-permissions)')
104
+ .option('--debug', 'Enable debug logging to ~/.aiexe/ (for pipe mode)')
105
+ .option('--sessionid <id>', 'Continue from specified session ID (for pipe mode)')
42
106
  .argument('[mission]', 'Natural language task description (e.g., "refactor auth module")')
43
107
  .action((mission, options) => {
44
108
  // 메인 커맨드 action 핸들러
@@ -58,6 +122,16 @@ Examples:
58
122
  Start payload viewer on default port (3300)
59
123
  $ aiexecode --viewer --port 8000
60
124
  Start payload viewer on custom port
125
+ $ aiexecode --dangerously-skip-permissions "build the project"
126
+ Run without tool approval prompts (use with caution)
127
+
128
+ Pipe Mode (for automation/scripting):
129
+ $ aiexecode -p "list files in current directory"
130
+ Run without UI, output session ID to stdout
131
+ $ aiexecode -p --debug "create a test file"
132
+ Run with debug logging to ~/.aiexe/
133
+ $ aiexecode -p --debug --sessionid abc1234567890def "now delete that file"
134
+ Continue from previous session context
61
135
 
62
136
  Available Slash Commands (in interactive mode):
63
137
  /help Show all available commands
@@ -88,7 +162,7 @@ Configuration:
88
162
  MCP servers: ~/.aiexe/mcp_config.json
89
163
 
90
164
  Supported AI Providers:
91
- - OpenAI (GPT-4, GPT-5)
165
+ - Z.AI (GLM-4.5, GLM-4.6, GLM-4.7)
92
166
 
93
167
  For more information, visit the project repository.
94
168
  `);
@@ -105,6 +179,11 @@ const shouldContinue = options.continue;
105
179
  const viewerMode = options.viewer;
106
180
  const initMode = options.init;
107
181
  const viewerPort = parseInt(options.port, 10);
182
+ const pipeMode = options.pipe || false;
183
+ const debugMode = options.debug || false;
184
+ const sessionIdOption = options.sessionid;
185
+ // pipe mode는 자동으로 dangerouslySkipPermissions를 포함
186
+ const dangerouslySkipPermissions = options.dangerouslySkipPermissions || pipeMode || false;
108
187
  let mission = args[0];
109
188
 
110
189
  // Init 모드 처리
@@ -167,9 +246,27 @@ if (viewerMode) {
167
246
  await new Promise(() => { });
168
247
  }
169
248
 
249
+ // ========================================
250
+ // 의존성 체크 (가장 먼저 수행)
251
+ // ========================================
252
+ // ripgrep, node, bash 등 필수 시스템 의존성을 확인
253
+ // Python은 선택사항이므로 여기서는 체크하지 않음 (나중에 settings 로드 후 확인)
254
+ const dependencyCheck = await checkDependencies({ skipPython: true });
255
+ if (!dependencyCheck.success) {
256
+ printDependencyError(dependencyCheck);
257
+ process.exit(1);
258
+ }
259
+
260
+ // 시스템 정보 수집 (의존성 체크 통과 후)
261
+ const initialSystemInfo = await getSystemInfo({ skipPython: true });
262
+
170
263
  // 전역 설정
171
264
  process.app_custom = {};
172
265
  process.app_custom.__dirname = dirname(fileURLToPath(import.meta.url));
266
+ process.app_custom.dangerouslySkipPermissions = dangerouslySkipPermissions;
267
+ process.app_custom.pipeMode = pipeMode;
268
+ process.app_custom.debugMode = debugMode;
269
+ process.app_custom.systemInfo = initialSystemInfo;
173
270
 
174
271
  // 개발 모드 감지: 현재 디렉토리에서 node index.js로 실행했는지 확인
175
272
  // (글로벌 설치 후 aiexecode 명령으로 실행 시에는 다른 경로에서 실행됨)
@@ -189,37 +286,53 @@ function generateSessionID() {
189
286
  }
190
287
 
191
288
  // Session ID 설정
192
- if (shouldContinue) {
193
- // --continue 모드일 때는 옵션 값이 session_id
194
- if (typeof shouldContinue !== 'string' || shouldContinue.length !== 16 || !/^[0-9a-f]{16}$/.test(shouldContinue)) {
195
- console.log(chalk.red('\nStartup Failed: Invalid Session ID Format\n'));
196
- console.log(chalk.yellow('Reason:'));
197
- console.log(' The session ID must be a 16-character hexadecimal string (0-9, a-f).');
198
- console.log(chalk.yellow('\nSolution:'));
199
- console.log(' 1. Check your session ID format - it should look like: abc1234567890def');
200
- console.log(' 2. Use the correct format with --continue option:');
201
- console.log(chalk.cyan(' aiexecode --continue abc1234567890def "your mission"'));
202
- console.log(' 3. Or start a new session without --continue option\n');
203
- process.exit(1);
289
+ // --continue (interactive mode) 또는 --sessionid (pipe mode) 옵션 처리
290
+ const continueSessionId = shouldContinue || sessionIdOption;
291
+ if (continueSessionId) {
292
+ // 세션 이어가기 모드
293
+ if (typeof continueSessionId !== 'string' || continueSessionId.length !== 16 || !/^[0-9a-f]{16}$/.test(continueSessionId)) {
294
+ if (pipeMode) {
295
+ // Pipe mode에서는 stderr로 에러 출력
296
+ console.error('[ERROR] Invalid session ID format. Must be 16-char hex (e.g., abc1234567890def)');
297
+ process.exit(2);
298
+ } else {
299
+ console.log(chalk.red('\nStartup Failed: Invalid Session ID Format\n'));
300
+ console.log(chalk.yellow('Reason:'));
301
+ console.log(' The session ID must be a 16-character hexadecimal string (0-9, a-f).');
302
+ console.log(chalk.yellow('\nSolution:'));
303
+ console.log(' 1. Check your session ID format - it should look like: abc1234567890def');
304
+ console.log(' 2. Use the correct format with --continue option:');
305
+ console.log(chalk.cyan(' aiexecode --continue abc1234567890def "your mission"'));
306
+ console.log(' 3. Or start a new session without --continue option\n');
307
+ process.exit(1);
308
+ }
204
309
  }
205
- process.app_custom.sessionID = shouldContinue;
310
+ process.app_custom.sessionID = continueSessionId;
206
311
  debugLog(`Continuing session ID: ${process.app_custom.sessionID}`);
207
312
  } else {
208
- // 일반 모드일 때는 새로운 session_id 생성
313
+ // 세션 시작
209
314
  process.app_custom.sessionID = generateSessionID();
210
315
  debugLog(`New session ID: ${process.app_custom.sessionID}`);
211
316
  }
212
317
 
213
- // 임시 폴더 정리
214
- await safeRm(PAYLOAD_LOG_DIR, { recursive: true, force: true }); // 홈 디렉토리의 .aiexe/payload_log (모든 모드)
215
- await safeRm(DEBUG_LOG_DIR, { recursive: true, force: true }); // 홈 디렉토리의 .aiexe/debuglog (모든 모드)
318
+ // 임시 폴더 정리 (debug 모드가 아닐 때만)
319
+ // debug 모드에서는 로그를 유지해야 하므로 정리하지 않음
320
+ if (!debugMode) {
321
+ await safeRm(PAYLOAD_LOG_DIR, { recursive: true, force: true }); // 홈 디렉토리의 .aiexe/payload_log
322
+ await safeRm(DEBUG_LOG_DIR, { recursive: true, force: true }); // 홈 디렉토리의 .aiexe/debuglog
323
+ }
216
324
 
217
325
  // 설정 로드 (시스템 정보 수집 전에 먼저 로드)
218
326
  await ensureConfigDirectory();
219
327
 
220
328
  // 초기 설정 확인 및 Setup Wizard 실행
329
+ // Pipe mode에서는 Setup Wizard 스킵 (설정이 없으면 에러)
221
330
  const configured = await isConfigured();
222
331
  if (!configured) {
332
+ if (pipeMode) {
333
+ console.error('[ERROR] Not configured. Run aiexecode interactively first to complete setup.');
334
+ process.exit(2);
335
+ }
223
336
  const setupCompleted = await runSetupWizard();
224
337
 
225
338
  if (!setupCompleted) {
@@ -237,48 +350,28 @@ if (!configured) {
237
350
 
238
351
  const settings = await loadSettings();
239
352
 
240
- // Python 도구 사용 여부 확인
241
- const skipPython = settings?.TOOLS_ENABLED?.run_python_code === false;
242
-
243
- // 시스템 정보 수집 (Python 도구가 비활성화된 경우 Python 체크 생략)
244
- process.app_custom.systemInfo = await getSystemInfo({ skipPython });
245
-
246
- // 의존성 체크 (flutter doctor와 유사)
247
- const dependencyCheck = await checkDependencies({ skipPython });
248
- if (!dependencyCheck.success) {
249
- console.log(chalk.red('\nStartup Failed: Missing Required Dependencies\n'));
250
- console.log(chalk.yellow('Reason:'));
251
- console.log(' One or more required system dependencies are missing or incompatible.\n');
252
-
253
- dependencyCheck.issues.forEach(issue => {
254
- if (issue.type === 'unsupported_os') {
255
- console.log(chalk.red(' [UNSUPPORTED] ') + issue.message);
256
- console.log(' ' + issue.details);
257
- } else if (issue.type === 'missing_command') {
258
- console.log(chalk.red(` [MISSING] ${issue.command}: `) + issue.message);
259
- console.log(chalk.cyan(` Install: ${issue.install}`));
260
- }
261
- });
262
-
263
- console.log(chalk.yellow('\nSolution:'));
264
- console.log(' 1. Install all missing dependencies listed above');
265
- console.log(' 2. Ensure all commands are accessible in your PATH');
266
- console.log(' 3. Restart your terminal after installation');
267
- console.log(' 4. Run aiexecode again\n');
268
- process.exit(1);
353
+ // --debug 옵션이 있으면 SHOW_API_PAYLOAD 활성화 (런타임에서만)
354
+ if (debugMode) {
355
+ settings.SHOW_API_PAYLOAD = true;
269
356
  }
270
357
 
271
- // 선택적 의존성 경고 표시
272
- if (dependencyCheck.warnings && dependencyCheck.warnings.length > 0) {
273
- debugLog('\n⚠️ Optional Dependencies\n');
274
-
275
- dependencyCheck.warnings.forEach(warning => {
276
- if (warning.type === 'optional_command') {
277
- debugLog(` ⚠ ${warning.command}: ${warning.message}`);
278
- debugLog(` Install: ${warning.install}`);
279
- debugLog(` Impact: ${warning.impact}`);
280
- }
281
- });
358
+ // Python 도구 사용 여부에 따른 시스템 정보 업데이트
359
+ const skipPython = settings?.TOOLS_ENABLED?.run_python_code === false;
360
+ if (!skipPython) {
361
+ // Python이 활성화된 경우 전체 시스템 정보 수집 (Python 포함)
362
+ process.app_custom.systemInfo = await getSystemInfo({ skipPython: false });
363
+
364
+ // Python 관련 경고 확인
365
+ const fullCheck = await checkDependencies({ skipPython: false });
366
+ if (fullCheck.warnings && fullCheck.warnings.length > 0) {
367
+ fullCheck.warnings.forEach(warning => {
368
+ if (warning.type === 'optional_command') {
369
+ debugLog(`Optional dependency missing: ${warning.command}`);
370
+ debugLog(` Install: ${warning.install.primary}`);
371
+ debugLog(` Disabled features: ${warning.disabledFeatures?.join(', ') || 'none'}`);
372
+ }
373
+ });
374
+ }
282
375
  }
283
376
 
284
377
  // 환경변수 설정
@@ -294,14 +387,16 @@ if (!process.env.REASONING_EFFORT && settings?.REASONING_EFFORT) {
294
387
 
295
388
  // 최종 검증
296
389
  if (!process.env.API_KEY) {
390
+ if (pipeMode) {
391
+ console.error('[ERROR] API_KEY not configured. Set it in ~/.aiexe/settings.json');
392
+ process.exit(2);
393
+ }
297
394
  console.log(chalk.red('\nStartup Failed: Missing API Key\n'));
298
395
  console.log(chalk.yellow('Reason:'));
299
396
  console.log(' API_KEY is not configured in the settings.');
300
397
  console.log(chalk.yellow('\nSolution:'));
301
- console.log(' 1. Obtain an API key from your provider (OpenAI, Google, Anthropic):');
302
- console.log(chalk.cyan(' OpenAI: https://platform.openai.com/account/api-keys'));
303
- console.log(chalk.cyan(' Google: https://makersuite.google.com/app/apikey'));
304
- console.log(chalk.cyan(' Anthropic: https://console.anthropic.com/settings/keys'));
398
+ console.log(' 1. Obtain an API key from Z.AI:');
399
+ console.log(chalk.cyan(' https://z.ai/manage-apikey/apikey-list'));
305
400
  console.log(` 2. Add the API key to your settings file:`);
306
401
  console.log(chalk.cyan(` ${SETTINGS_FILE}`));
307
402
  console.log(' 3. Or run the setup wizard again by deleting the settings file');
@@ -309,6 +404,12 @@ if (!process.env.API_KEY) {
309
404
  process.exit(1);
310
405
  }
311
406
 
407
+ // Pipe mode에서는 mission이 필수
408
+ if (pipeMode && (!mission || !mission.trim())) {
409
+ console.error('[ERROR] Mission is required in pipe mode. Usage: node index.js -p "your mission"');
410
+ process.exit(2);
411
+ }
412
+
312
413
  // ========================================
313
414
  // MCP Integration 초기화
314
415
  // ========================================
@@ -451,7 +552,34 @@ async function handleSubmit(text) {
451
552
  // 슬래시 커맨드 처리
452
553
  if (isCommand(text)) {
453
554
  try {
454
- await commandRegistry.execute(text);
555
+ const result = await commandRegistry.execute(text);
556
+
557
+ // 커스텀 커맨드가 반환된 경우 AI에게 전달하여 실행
558
+ if (result && result.type === 'custom_command') {
559
+ debugLog(`Custom command invoked: ${result.commandName}`);
560
+ uiEvents.addSystemMessage(`📋 Command: ${result.commandName}`);
561
+
562
+ // 커맨드의 프롬프트를 미션으로 사용하여 세션 실행
563
+ await runSession({
564
+ mission: result.prompt,
565
+ maxIterations: 50,
566
+ mcpToolSchemas,
567
+ mcpToolFunctions
568
+ });
569
+ }
570
+ // 스킬이 반환된 경우 AI에게 전달하여 실행
571
+ else if (result && result.type === 'skill') {
572
+ debugLog(`Skill invoked: ${result.skillName}`);
573
+ uiEvents.addSkillInvoked(result.skillName);
574
+
575
+ // 스킬의 프롬프트를 미션으로 사용하여 세션 실행
576
+ await runSession({
577
+ mission: result.prompt,
578
+ maxIterations: 50,
579
+ mcpToolSchemas,
580
+ mcpToolFunctions
581
+ });
582
+ }
455
583
  } catch (err) {
456
584
  debugLog(`Command execution error: ${err.message}`);
457
585
  uiEvents.addErrorMessage(`${err.message}\nType /help to see available commands`);
@@ -462,8 +590,12 @@ async function handleSubmit(text) {
462
590
  // 세션 실행 (AI Agent의 미션 수행)
463
591
  // 시작/종료 알림 및 저장은 runSession 내부에서 처리
464
592
  try {
593
+ // 파일 참조 파싱 (@경로 -> 참조된 파일 목록으로 변환)
594
+ const parsed = await parseFileReferences(text);
595
+ const missionText = parsed.hasReferences ? parsed.transformedMessage : text;
596
+
465
597
  await runSession({
466
- mission: text, // 사용자가 입력한 미션
598
+ mission: missionText, // 변환된 미션 (파일 참조 포함 시)
467
599
  maxIterations: 50, // 최대 반복 횟수
468
600
  mcpToolSchemas, // AI 모델에 전달할 MCP 도구 스키마들
469
601
  mcpToolFunctions // 실제 MCP 도구 실행 함수들
@@ -476,28 +608,54 @@ async function handleSubmit(text) {
476
608
  const errorType = err?.type || err?.constructor?.name || 'Error';
477
609
  const errorStack = err?.stack || 'No stack trace available';
478
610
 
611
+ // debug 설정 확인
612
+ const settings = await loadSettings().catch(() => ({}));
613
+ const isDebugMode = settings?.SHOW_API_PAYLOAD === true;
614
+
479
615
  // 인증 에러인 경우 설정 파일 안내
480
616
  if (err?.code === 'invalid_api_key' || err?.status === 401) {
481
- const consolidatedErrorMessage = [
482
- `[Main] Authentication failed: ${errorMessage}`,
483
- ` ├─ Code: ${errorCode}`,
484
- ` ├─ Status: ${errorStatus}`,
485
- ` ├─ Type: ${errorType}`,
486
- ` └─ Stack trace: ${errorStack}`
487
- ].join('\n');
488
- uiEvents.addErrorMessage(consolidatedErrorMessage);
489
- uiEvents.addSystemMessage(`💡 Please check your API key in ${SETTINGS_FILE}`);
617
+ if (isDebugMode) {
618
+ const consolidatedErrorMessage = [
619
+ `[Main] Authentication failed: ${errorMessage}`,
620
+ ` ├─ Code: ${errorCode}`,
621
+ ` ├─ Status: ${errorStatus}`,
622
+ ` ├─ Type: ${errorType}`,
623
+ ` └─ Stack trace: ${errorStack}`
624
+ ].join('\n');
625
+ uiEvents.addErrorMessage(consolidatedErrorMessage);
626
+ } else {
627
+ uiEvents.addErrorMessage('[Main] API 키가 유효하지 않습니다.');
628
+ }
629
+ uiEvents.addSystemMessage(`Please check your API key in ${SETTINGS_FILE}`);
490
630
  } else {
491
631
  // 일반 에러
492
- const consolidatedErrorMessage = [
493
- `[Main] Session execution error: ${errorMessage}`,
494
- ` ├─ Code: ${errorCode}`,
495
- ` ├─ Status: ${errorStatus}`,
496
- ` ├─ Type: ${errorType}`,
497
- ` ├─ Error Object: ${JSON.stringify(err, null, 2)}`,
498
- ` └─ Stack trace: ${errorStack}`
499
- ].join('\n');
500
- uiEvents.addErrorMessage(consolidatedErrorMessage);
632
+ if (isDebugMode) {
633
+ const consolidatedErrorMessage = [
634
+ `[Main] Session execution error: ${errorMessage}`,
635
+ ` ├─ Code: ${errorCode}`,
636
+ ` ├─ Status: ${errorStatus}`,
637
+ ` ├─ Type: ${errorType}`,
638
+ ` ├─ Error Object: ${JSON.stringify(err, null, 2)}`,
639
+ ` └─ Stack trace: ${errorStack}`
640
+ ].join('\n');
641
+ uiEvents.addErrorMessage(consolidatedErrorMessage);
642
+ } else {
643
+ // 사용자 친화적 에러 메시지
644
+ const statusNum = parseInt(errorStatus) || parseInt(err?.status);
645
+ let userFriendlyMessage;
646
+
647
+ if (statusNum === 503 || errorMessage.includes('503')) {
648
+ userFriendlyMessage = 'AI 서버가 일시적으로 응답하지 않습니다. 잠시 후 다시 시도해주세요.';
649
+ } else if (statusNum === 500 || errorMessage.includes('500')) {
650
+ userFriendlyMessage = 'AI 서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
651
+ } else if (statusNum === 429) {
652
+ userFriendlyMessage = '요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요.';
653
+ } else {
654
+ userFriendlyMessage = '오류가 발생했습니다. 문제가 지속되면 /debug on 으로 상세 정보를 확인하세요.';
655
+ }
656
+
657
+ uiEvents.addErrorMessage(`[Main] ${userFriendlyMessage}`);
658
+ }
501
659
  }
502
660
  }
503
661
  }
@@ -514,9 +672,75 @@ async function handleExit() {
514
672
  });
515
673
  }
516
674
 
675
+ // ========================================
676
+ // Pipe Mode 처리
677
+ // ========================================
678
+ // UI 없이 직접 세션 실행하고 sessionid 출력 후 종료
679
+ if (pipeMode) {
680
+ debugLog(`[PipeMode] Starting pipe mode execution`);
681
+ debugLog(`[PipeMode] Mission: ${mission}`);
682
+ debugLog(`[PipeMode] SessionID: ${process.app_custom.sessionID}`);
683
+ debugLog(`[PipeMode] Debug: ${debugMode}`);
684
+
685
+ try {
686
+ // MCP 초기화 완료 대기
687
+ await mcpInitPromise;
688
+ debugLog(`[PipeMode] MCP initialization complete`);
689
+
690
+ // 이전 세션 로드 (--sessionid 옵션)
691
+ let previousSessions = null;
692
+ if (sessionIdOption) {
693
+ previousSessions = await loadPreviousSessions(process.app_custom.sessionID);
694
+ if (previousSessions && previousSessions.length > 0) {
695
+ debugLog(`[PipeMode] Loaded ${previousSessions.length} previous session(s)`);
696
+ } else {
697
+ debugLog(`[PipeMode] No previous session found for ID: ${process.app_custom.sessionID}`);
698
+ }
699
+ }
700
+
701
+ // 파일 참조 파싱
702
+ const parsed = await parseFileReferences(mission);
703
+ const missionText = parsed.hasReferences ? parsed.transformedMessage : mission;
704
+
705
+ // 세션 실행
706
+ const result = await runSession({
707
+ mission: missionText,
708
+ maxIterations: 50,
709
+ mcpToolSchemas,
710
+ mcpToolFunctions,
711
+ previousSessions
712
+ });
713
+
714
+ // stdout에 sessionid만 출력 (다른 모든 출력은 stderr나 로그 파일로)
715
+ console.log(process.app_custom.sessionID);
716
+
717
+ // MCP 정리
718
+ if (mcpIntegration) {
719
+ await mcpIntegration.cleanup();
720
+ }
721
+
722
+ // 종료 (성공=0, 미션 미완료=1)
723
+ process.exit(result?.mission_solved ? 0 : 1);
724
+ } catch (error) {
725
+ console.error(`[ERROR] ${error.message}`);
726
+ debugLog(`[PipeMode] Fatal error: ${error.stack}`);
727
+
728
+ // MCP 정리 시도
729
+ if (mcpIntegration) {
730
+ await mcpIntegration.cleanup().catch(() => {});
731
+ }
732
+
733
+ process.exit(2);
734
+ }
735
+ }
736
+
737
+ // ========================================
738
+ // Interactive Mode (UI)
739
+ // ========================================
740
+
517
741
  // 히스토리 복원 (--continue 옵션)
518
742
  let initialHistory = [];
519
- if (shouldContinue) {
743
+ if (continueSessionId) {
520
744
  const previousSessions = await loadPreviousSessions(process.app_custom.sessionID);
521
745
  if (previousSessions && previousSessions.length > 0) {
522
746
  debugLog(`Loaded ${previousSessions.length} session(s) from history`);
@@ -566,7 +790,7 @@ const versionCheckPromise = checkForUpdates(VERSION).then(info => {
566
790
 
567
791
  // UI 시작
568
792
  const currentModel = await getModelForProvider();
569
- const currentReasoningEffort = settings?.OPENAI_REASONING_EFFORT || process.env.OPENAI_REASONING_EFFORT;
793
+ const currentReasoningEffort = settings?.REASONING_EFFORT || process.env.REASONING_EFFORT;
570
794
  uiInstance = startUI({
571
795
  onSubmit: handleSubmit,
572
796
  onClearScreen: handleClearScreen,
@@ -9,8 +9,9 @@
9
9
  * @version 1.0.0
10
10
  */
11
11
 
12
- import fs from 'fs';
12
+ import { existsSync } from 'fs';
13
13
  import path from 'path';
14
+ import { safeMkdirSync, safeWriteFile } from '../../src/util/safe_fs.js';
14
15
 
15
16
  /**
16
17
  * 타임스탬프를 파일명 형식으로 포맷
@@ -94,9 +95,9 @@ export class MCPMessageLogger {
94
95
  */
95
96
  _initializeLogDir() {
96
97
  try {
97
- // 로그 디렉토리 생성
98
- if (!fs.existsSync(this.options.logDir)) {
99
- fs.mkdirSync(this.options.logDir, { recursive: true });
98
+ // 로그 디렉토리 생성 (safe_fs 사용 - ALLOWED_DIRECTORIES 검증)
99
+ if (!existsSync(this.options.logDir)) {
100
+ safeMkdirSync(this.options.logDir, { recursive: true });
100
101
  }
101
102
  } catch (error) {
102
103
  console.error(`Failed to initialize log directory: ${error.message}`);
@@ -105,14 +106,14 @@ export class MCPMessageLogger {
105
106
  }
106
107
 
107
108
  /**
108
- * 개별 메시지를 JSON 파일로 저장
109
+ * 개별 메시지를 JSON 파일로 저장 (safe_fs 사용 - ALLOWED_DIRECTORIES 검증)
109
110
  */
110
- _writeMessageToFile(direction, serverName, message, metadata) {
111
+ async _writeMessageToFile(direction, serverName, message, metadata) {
111
112
  try {
112
- // 서버별 하위 디렉토리 생성
113
+ // 서버별 하위 디렉토리 생성 (safe_fs 사용)
113
114
  const serverLogDir = path.join(this.options.logDir, serverName);
114
- if (!fs.existsSync(serverLogDir)) {
115
- fs.mkdirSync(serverLogDir, { recursive: true });
115
+ if (!existsSync(serverLogDir)) {
116
+ safeMkdirSync(serverLogDir, { recursive: true });
116
117
  }
117
118
 
118
119
  // 순차 번호 획득 및 증가
@@ -132,7 +133,7 @@ export class MCPMessageLogger {
132
133
  ? JSON.stringify(logData, null, 2)
133
134
  : JSON.stringify(logData);
134
135
 
135
- fs.writeFileSync(filepath, content, 'utf-8');
136
+ await safeWriteFile(filepath, content, 'utf-8');
136
137
  } catch (error) {
137
138
  console.error(`Failed to write message file: ${error.message}`);
138
139
  }
@@ -150,9 +151,9 @@ export class MCPMessageLogger {
150
151
 
151
152
  this.stats.sent++;
152
153
 
153
- // 파일로 저장
154
+ // 파일로 저장 (fire-and-forget, 에러는 내부에서 처리됨)
154
155
  if (this.options.logDir && (this.options.output === 'file' || this.options.output === 'both')) {
155
- this._writeMessageToFile('SEND', serverName, message, metadata);
156
+ this._writeMessageToFile('SEND', serverName, message, metadata).catch(() => {});
156
157
  }
157
158
 
158
159
  // 콘솔 출력
@@ -187,9 +188,9 @@ export class MCPMessageLogger {
187
188
 
188
189
  this.stats.received++;
189
190
 
190
- // 파일로 저장
191
+ // 파일로 저장 (fire-and-forget, 에러는 내부에서 처리됨)
191
192
  if (this.options.logDir && (this.options.output === 'file' || this.options.output === 'both')) {
192
- this._writeMessageToFile('RECV', serverName, message, metadata);
193
+ this._writeMessageToFile('RECV', serverName, message, metadata).catch(() => {});
193
194
  }
194
195
 
195
196
  // 콘솔 출력
@@ -230,9 +231,9 @@ export class MCPMessageLogger {
230
231
  code: error.code
231
232
  };
232
233
 
233
- // 파일로 저장
234
+ // 파일로 저장 (fire-and-forget, 에러는 내부에서 처리됨)
234
235
  if (this.options.logDir && (this.options.output === 'file' || this.options.output === 'both')) {
235
- this._writeMessageToFile('ERROR', serverName, errorMessage, metadata);
236
+ this._writeMessageToFile('ERROR', serverName, errorMessage, metadata).catch(() => {});
236
237
  }
237
238
 
238
239
  // 콘솔 출력
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aiexecode",
3
- "version": "1.0.94",
4
- "description": "A CLI-based AI Coding Agent",
3
+ "version": "1.0.127",
4
+ "description": "Your intelligent coding companion that thinks, plans, and builds with you",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "license": "SEE LICENSE IN LICENSE",
@@ -21,8 +21,8 @@
21
21
  "LICENSE"
22
22
  ],
23
23
  "engines": {
24
- "node": ">= 14.0.0",
25
- "npm": ">= 6.0.0"
24
+ "node": ">= 18.0.0",
25
+ "npm": ">= 8.0.0"
26
26
  },
27
27
  "homepage": "https://aiexecode.com",
28
28
  "repository": {