aiexecode 1.0.157

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 (188) hide show
  1. package/LICENSE +68 -0
  2. package/README.md +347 -0
  3. package/config_template/mcp_config.json +3 -0
  4. package/config_template/package_name_store.json +5 -0
  5. package/config_template/settings.json +5 -0
  6. package/index.js +879 -0
  7. package/mcp-agent-lib/example/01-basic-usage.js +82 -0
  8. package/mcp-agent-lib/example/02-quick-start.js +52 -0
  9. package/mcp-agent-lib/example/03-http-server.js +76 -0
  10. package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
  11. package/mcp-agent-lib/example/05-error-handling.js +116 -0
  12. package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
  13. package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
  14. package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
  15. package/mcp-agent-lib/example/README.md +346 -0
  16. package/mcp-agent-lib/index.js +19 -0
  17. package/mcp-agent-lib/init.sh +3 -0
  18. package/mcp-agent-lib/package-lock.json +1216 -0
  19. package/mcp-agent-lib/package.json +53 -0
  20. package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
  21. package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
  22. package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
  23. package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
  24. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
  25. package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
  26. package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
  27. package/mcp-agent-lib/sampleMCPHost/index.js +386 -0
  28. package/mcp-agent-lib/sampleMCPHost/mcp_config.json +24 -0
  29. package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
  30. package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
  31. package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
  32. package/mcp-agent-lib/src/mcp_client.js +1860 -0
  33. package/mcp-agent-lib/src/mcp_message_logger.js +517 -0
  34. package/package.json +72 -0
  35. package/payload_viewer/out/404/index.html +1 -0
  36. package/payload_viewer/out/404.html +1 -0
  37. package/payload_viewer/out/_next/static/chunks/060f9a97930f3d04.js +1 -0
  38. package/payload_viewer/out/_next/static/chunks/103c802c8f4a5ea1.js +1 -0
  39. package/payload_viewer/out/_next/static/chunks/16474fd6c6910c45.js +1 -0
  40. package/payload_viewer/out/_next/static/chunks/17722e3ac4e00587.js +1 -0
  41. package/payload_viewer/out/_next/static/chunks/305b077a9873cf54.js +1 -0
  42. package/payload_viewer/out/_next/static/chunks/4c1d05c6741c2bdd.js +5 -0
  43. package/payload_viewer/out/_next/static/chunks/538cc02e54714b23.js +1 -0
  44. package/payload_viewer/out/_next/static/chunks/6251fa5907d2b226.js +5 -0
  45. package/payload_viewer/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
  46. package/payload_viewer/out/_next/static/chunks/b6c0459f3789d25c.js +1 -0
  47. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  48. package/payload_viewer/out/_next/static/chunks/bd2dcf98c9b362f6.js +1 -0
  49. package/payload_viewer/out/_next/static/chunks/c8a542ae21335479.js +1 -0
  50. package/payload_viewer/out/_next/static/chunks/cdd12d5c1a5a6064.js +1 -0
  51. package/payload_viewer/out/_next/static/chunks/e411019f55d87c42.js +1 -0
  52. package/payload_viewer/out/_next/static/chunks/e60ef129113f6e24.js +1 -0
  53. package/payload_viewer/out/_next/static/chunks/f1ac9047ac4a3fde.js +1 -0
  54. package/payload_viewer/out/_next/static/chunks/turbopack-0ac29803ce3c3c7a.js +3 -0
  55. package/payload_viewer/out/_next/static/chunks/turbopack-89db4c64206a73e4.js +3 -0
  56. package/payload_viewer/out/_next/static/chunks/turbopack-a5b8235fa59d7119.js +3 -0
  57. package/payload_viewer/out/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
  58. package/payload_viewer/out/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
  59. package/payload_viewer/out/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
  60. package/payload_viewer/out/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
  61. package/payload_viewer/out/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
  62. package/payload_viewer/out/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
  63. package/payload_viewer/out/_next/static/media/favicon.0b3bf435.ico +0 -0
  64. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_buildManifest.js +14 -0
  65. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_clientMiddlewareManifest.json +1 -0
  66. package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_ssgManifest.js +1 -0
  67. package/payload_viewer/out/favicon.ico +0 -0
  68. package/payload_viewer/out/file.svg +1 -0
  69. package/payload_viewer/out/globe.svg +1 -0
  70. package/payload_viewer/out/index.html +1 -0
  71. package/payload_viewer/out/index.txt +23 -0
  72. package/payload_viewer/out/next.svg +1 -0
  73. package/payload_viewer/out/vercel.svg +1 -0
  74. package/payload_viewer/out/window.svg +1 -0
  75. package/payload_viewer/web_server.js +861 -0
  76. package/prompts/completion_judge.txt +128 -0
  77. package/prompts/orchestrator.txt +1213 -0
  78. package/src/LLMClient/client.js +1375 -0
  79. package/src/LLMClient/converters/input-normalizer.js +238 -0
  80. package/src/LLMClient/converters/responses-to-claude.js +503 -0
  81. package/src/LLMClient/converters/responses-to-gemini.js +648 -0
  82. package/src/LLMClient/converters/responses-to-ollama.js +348 -0
  83. package/src/LLMClient/converters/responses-to-zai.js +667 -0
  84. package/src/LLMClient/errors.js +398 -0
  85. package/src/LLMClient/index.js +36 -0
  86. package/src/ai_based/completion_judge.js +421 -0
  87. package/src/ai_based/orchestrator.js +527 -0
  88. package/src/ai_based/pip_package_installer.js +173 -0
  89. package/src/ai_based/pip_package_lookup.js +197 -0
  90. package/src/cli/mcp_cli.js +70 -0
  91. package/src/cli/mcp_commands.js +255 -0
  92. package/src/commands/agents.js +18 -0
  93. package/src/commands/apikey.js +55 -0
  94. package/src/commands/bg.js +140 -0
  95. package/src/commands/commands.js +56 -0
  96. package/src/commands/debug.js +54 -0
  97. package/src/commands/exit.js +19 -0
  98. package/src/commands/help.js +35 -0
  99. package/src/commands/mcp.js +128 -0
  100. package/src/commands/model.js +176 -0
  101. package/src/commands/setup.js +13 -0
  102. package/src/commands/skills.js +51 -0
  103. package/src/commands/tools.js +165 -0
  104. package/src/commands/viewer.js +147 -0
  105. package/src/config/ai_models.js +312 -0
  106. package/src/config/config.js +10 -0
  107. package/src/config/constants.js +71 -0
  108. package/src/config/feature_flags.js +15 -0
  109. package/src/frontend/App.js +1263 -0
  110. package/src/frontend/README.md +81 -0
  111. package/src/frontend/components/AutocompleteMenu.js +47 -0
  112. package/src/frontend/components/BackgroundProcessList.js +175 -0
  113. package/src/frontend/components/BlankLine.js +62 -0
  114. package/src/frontend/components/ConversationItem.js +893 -0
  115. package/src/frontend/components/CurrentModelView.js +43 -0
  116. package/src/frontend/components/FileDiffViewer.js +616 -0
  117. package/src/frontend/components/Footer.js +25 -0
  118. package/src/frontend/components/Header.js +42 -0
  119. package/src/frontend/components/HelpView.js +154 -0
  120. package/src/frontend/components/Input.js +344 -0
  121. package/src/frontend/components/LoadingIndicator.js +31 -0
  122. package/src/frontend/components/ModelListView.js +49 -0
  123. package/src/frontend/components/ModelUpdatedView.js +22 -0
  124. package/src/frontend/components/SessionSpinner.js +66 -0
  125. package/src/frontend/components/SetupWizard.js +242 -0
  126. package/src/frontend/components/StreamOutput.js +34 -0
  127. package/src/frontend/components/TodoList.js +56 -0
  128. package/src/frontend/components/ToolApprovalPrompt.js +452 -0
  129. package/src/frontend/design/themeColors.js +42 -0
  130. package/src/frontend/hooks/useCompletion.js +84 -0
  131. package/src/frontend/hooks/useFileCompletion.js +467 -0
  132. package/src/frontend/hooks/useKeypress.js +145 -0
  133. package/src/frontend/index.js +65 -0
  134. package/src/frontend/utils/GridRenderer.js +140 -0
  135. package/src/frontend/utils/InlineFormatter.js +156 -0
  136. package/src/frontend/utils/diffUtils.js +235 -0
  137. package/src/frontend/utils/inputBuffer.js +441 -0
  138. package/src/frontend/utils/markdownParser.js +377 -0
  139. package/src/frontend/utils/outputRedirector.js +47 -0
  140. package/src/frontend/utils/renderInkComponent.js +42 -0
  141. package/src/frontend/utils/syntaxHighlighter.js +149 -0
  142. package/src/frontend/utils/toolUIFormatter.js +261 -0
  143. package/src/system/agents_loader.js +170 -0
  144. package/src/system/ai_request.js +737 -0
  145. package/src/system/background_process.js +317 -0
  146. package/src/system/code_executer.js +1233 -0
  147. package/src/system/command_loader.js +40 -0
  148. package/src/system/command_parser.js +133 -0
  149. package/src/system/conversation_state.js +265 -0
  150. package/src/system/conversation_trimmer.js +265 -0
  151. package/src/system/custom_command_loader.js +395 -0
  152. package/src/system/file_integrity.js +466 -0
  153. package/src/system/import_analyzer.py +174 -0
  154. package/src/system/log.js +82 -0
  155. package/src/system/mcp_integration.js +304 -0
  156. package/src/system/output_helper.js +89 -0
  157. package/src/system/session.js +1393 -0
  158. package/src/system/session_memory.js +481 -0
  159. package/src/system/skill_loader.js +324 -0
  160. package/src/system/system_info.js +483 -0
  161. package/src/system/tool_approval.js +160 -0
  162. package/src/system/tool_registry.js +184 -0
  163. package/src/system/ui_events.js +279 -0
  164. package/src/tools/code_editor.js +792 -0
  165. package/src/tools/file_reader.js +385 -0
  166. package/src/tools/glob.js +263 -0
  167. package/src/tools/response_message.js +30 -0
  168. package/src/tools/ripgrep.js +554 -0
  169. package/src/tools/skill_tool.js +122 -0
  170. package/src/tools/todo_write.js +182 -0
  171. package/src/tools/web_download.py +74 -0
  172. package/src/tools/web_downloader.js +83 -0
  173. package/src/util/clone.js +174 -0
  174. package/src/util/config.js +203 -0
  175. package/src/util/config_migration.js +174 -0
  176. package/src/util/debug_log.js +49 -0
  177. package/src/util/exit_handler.js +53 -0
  178. package/src/util/file_reference_parser.js +132 -0
  179. package/src/util/mcp_config_manager.js +159 -0
  180. package/src/util/output_formatter.js +50 -0
  181. package/src/util/path_helper.js +27 -0
  182. package/src/util/path_validator.js +178 -0
  183. package/src/util/prompt_loader.js +184 -0
  184. package/src/util/rag_helper.js +101 -0
  185. package/src/util/safe_fs.js +645 -0
  186. package/src/util/setup_wizard.js +62 -0
  187. package/src/util/text_formatter.js +33 -0
  188. package/src/util/version_check.js +116 -0
package/index.js ADDED
@@ -0,0 +1,879 @@
1
+ #!/usr/bin/env node
2
+ // 이 파일은 계획·실행·검증으로 이어지는 전체 에이전트 사이클을 조립하여 단일 작업 흐름으로 실행합니다.
3
+ import { dirname, join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { readFileSync, existsSync } from 'fs';
6
+ import { initializeMCPIntegration } from "./src/system/mcp_integration.js";
7
+ import { ensureConfigDirectory, loadSettings, SETTINGS_FILE, PAYLOAD_LOG_DIR, DEBUG_LOG_DIR } from "./src/util/config.js";
8
+ import { runSession } from "./src/system/session.js";
9
+ import { CommandRegistry, isCommand } from "./src/system/command_parser.js";
10
+ import { loadCommands } from "./src/system/command_loader.js";
11
+ import { discoverAllSkills } from "./src/system/skill_loader.js";
12
+ import { discoverAllCustomCommands } from "./src/system/custom_command_loader.js";
13
+ import { getSystemInfo, checkDependencies } from "./src/system/system_info.js";
14
+ import { loadPreviousSessions, reconstructUIHistory, deleteHistoryFile } from "./src/system/session_memory.js";
15
+ import { getModelForProvider } from "./src/system/ai_request.js";
16
+ import { runSetupWizard, isConfigured } from "./src/util/setup_wizard.js";
17
+ import { safeRm, safeMkdir, safeCopyFile, safeReaddir } from './src/util/safe_fs.js';
18
+ import { parseFileReferences } from './src/util/file_reference_parser.js';
19
+ import chalk from 'chalk';
20
+ import { startUI } from './src/frontend/index.js';
21
+ import { uiEvents } from './src/system/ui_events.js';
22
+ import { performExit } from './src/util/exit_handler.js';
23
+ import { Command } from 'commander';
24
+ import { registerMcpCliCommands } from './src/cli/mcp_cli.js';
25
+ import { createDebugLogger } from './src/util/debug_log.js';
26
+ import { checkForUpdates } from './src/util/version_check.js';
27
+ const debugLog = createDebugLogger('index.log', 'index');
28
+
29
+ /**
30
+ * 의존성 오류를 출력합니다.
31
+ */
32
+ function printDependencyError(check) {
33
+ const { osInfo, issues } = check;
34
+
35
+ console.log('');
36
+ console.log(chalk.bold.red('Missing Required Dependencies'));
37
+ console.log('');
38
+
39
+ // OS 정보
40
+ const osName = osInfo.name || check.os;
41
+ const osVersion = osInfo.version ? ` ${osInfo.version}` : '';
42
+ const pkgMgr = osInfo.packageManagerName ? ` (${osInfo.packageManagerName})` : '';
43
+ console.log(chalk.dim(`System: ${osName}${osVersion}${pkgMgr}`));
44
+ console.log('');
45
+
46
+ // 각 이슈 출력
47
+ issues.forEach((issue, idx) => {
48
+ if (issue.type === 'unsupported_os') {
49
+ console.log(chalk.red(`✗ ${issue.message}`));
50
+ console.log(chalk.dim(` ${issue.details}`));
51
+
52
+ if (issue.suggestions && issue.suggestions.length > 0) {
53
+ console.log('');
54
+ console.log(chalk.yellow('Alternatives:'));
55
+ issue.suggestions.forEach(suggestion => {
56
+ console.log(chalk.dim(` • ${suggestion}`));
57
+ });
58
+ }
59
+ } else if (issue.type === 'missing_command') {
60
+ console.log(chalk.red(`✗ ${issue.displayName || issue.command}`));
61
+ if (issue.description) {
62
+ console.log(chalk.dim(` ${issue.description}`));
63
+ }
64
+
65
+ console.log(chalk.green(' Install:'));
66
+ console.log(chalk.cyan(` $ ${issue.install.primary}`));
67
+
68
+ if (issue.install.alternatives && issue.install.alternatives.length > 0) {
69
+ issue.install.alternatives.forEach(alt => {
70
+ console.log(chalk.dim(` $ ${alt}`));
71
+ });
72
+ }
73
+
74
+ if (issue.install.url) {
75
+ console.log(chalk.dim(` More info: ${issue.install.url}`));
76
+ }
77
+ }
78
+
79
+ if (idx < issues.length - 1) {
80
+ console.log('');
81
+ }
82
+ });
83
+
84
+ console.log('');
85
+ console.log(chalk.yellow('After installing, restart your terminal and run aiexecode again.'));
86
+ console.log('');
87
+ }
88
+
89
+ // Read version from package.json
90
+ const __dirname = dirname(fileURLToPath(import.meta.url));
91
+ const packageJson = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf-8'));
92
+ const VERSION = packageJson.version;
93
+
94
+ // CLI 옵션 파싱
95
+ const program = new Command();
96
+ program
97
+ .name('aiexecode')
98
+ .description('AI-powered autonomous coding agent that executes development tasks through natural language missions')
99
+ .version(VERSION)
100
+ .option('-c, --continue <session_id>', 'Continue from previous session (16-char hex session ID)')
101
+ .option('--viewer', 'Start payload viewer web server')
102
+ .option('--port <port>', 'Port for payload viewer (default: 3000)', '3000')
103
+ .option('--init', 'Initialize project-specific prompts in current directory')
104
+ .option('--dangerously-skip-permissions', 'Skip all tool approval prompts (use with caution)')
105
+ .option('-p, --pipe', 'Pipe mode: non-interactive execution without REPL (includes --dangerously-skip-permissions)')
106
+ .option('--debug', 'Enable debug logging to ~/.aiexe/ (for pipe mode)')
107
+ .option('--sessionid <id>', 'Continue from specified session ID (for pipe mode)')
108
+ .argument('[mission]', 'Natural language task description (e.g., "refactor auth module")')
109
+ .action((mission, options) => {
110
+ // 메인 커맨드 action 핸들러
111
+ // Commander가 서브커맨드와 메인 커맨드를 구분할 수 있도록 함
112
+ // 실제 처리는 parse() 이후 기존 코드에서 수행
113
+ })
114
+ .addHelpText('after', `
115
+ Examples:
116
+ $ aiexecode "fix all linting errors"
117
+ $ aiexecode "add unit tests for authentication"
118
+ $ aiexecode -c abc1234567890def "continue with deployment setup"
119
+ $ aiexecode
120
+ Interactive mode - enter missions and commands in the UI
121
+ $ aiexecode --init
122
+ Initialize project-specific prompts in .aiexe/prompts/
123
+ $ aiexecode --viewer
124
+ Start payload viewer on default port (3000)
125
+ $ aiexecode --viewer --port 8000
126
+ Start payload viewer on custom port
127
+ $ aiexecode --dangerously-skip-permissions "build the project"
128
+ Run without tool approval prompts (use with caution)
129
+
130
+ Pipe Mode (for automation/scripting):
131
+ $ aiexecode -p "list files in current directory"
132
+ Run without UI, output session ID to stdout
133
+ $ aiexecode -p --debug "create a test file"
134
+ Run with debug logging to ~/.aiexe/
135
+ $ aiexecode -p --debug --sessionid abc1234567890def "now delete that file"
136
+ Continue from previous session context
137
+
138
+ Available Slash Commands (in interactive mode):
139
+ /help Show all available commands
140
+ /exit Exit the application
141
+ /clear Clear the screen
142
+ /apikey Manage API keys configuration
143
+ /mcp Manage MCP server connections and status
144
+
145
+ MCP (Model Context Protocol) Commands:
146
+ $ aiexecode mcp add --transport stdio <name> -- <command>
147
+ Add a stdio-based MCP server
148
+ $ aiexecode mcp add --transport http <name> <url>
149
+ Add an HTTP-based MCP server
150
+ $ aiexecode mcp add-json <name> '{"type":"http","url":"..."}'
151
+ Add an MCP server from JSON configuration
152
+ $ aiexecode mcp list
153
+ List all configured MCP servers
154
+ $ aiexecode mcp get <name>
155
+ Get details of a specific MCP server
156
+ $ aiexecode mcp remove <name>
157
+ Remove an MCP server
158
+
159
+ Configuration:
160
+ Settings are stored in ~/.aiexe/settings.json
161
+ Sessions are saved in <project_dir>/.aiexe/<session_id>/
162
+ Execution logs are saved in ~/.aiexe/payload_log/
163
+ Project-specific prompts: <project_dir>/.aiexe/prompts/ (optional)
164
+ MCP servers: ~/.aiexe/mcp_config.json
165
+
166
+ Supported AI Providers:
167
+ - Z.AI (GLM-4.5, GLM-4.6, GLM-4.7)
168
+
169
+ For more information, visit the project repository.
170
+ `);
171
+
172
+ // MCP CLI 명령어 등록
173
+ registerMcpCliCommands(program);
174
+
175
+ // 항상 파싱 수행 (action 핸들러가 있으므로 안전)
176
+ program.parse(process.argv);
177
+
178
+ const options = program.opts();
179
+ const args = program.args;
180
+ const shouldContinue = options.continue;
181
+ const viewerMode = options.viewer;
182
+ const initMode = options.init;
183
+ const viewerPort = parseInt(options.port, 10);
184
+ const pipeMode = options.pipe || false;
185
+ const debugMode = options.debug || false;
186
+ const sessionIdOption = options.sessionid;
187
+ // pipe mode는 자동으로 dangerouslySkipPermissions를 포함
188
+ const dangerouslySkipPermissions = options.dangerouslySkipPermissions || pipeMode || false;
189
+ let mission = args[0];
190
+
191
+ // Init 모드 처리
192
+ if (initMode) {
193
+ console.log(chalk.cyan('Initializing project-specific prompts...'));
194
+
195
+ // 현재 작업 디렉토리
196
+ const cwd = process.cwd();
197
+ const targetPromptsDir = join(cwd, '.aiexe', 'prompts');
198
+
199
+ // 프로젝트 루트의 prompts 디렉토리
200
+ const projectRoot = dirname(fileURLToPath(import.meta.url));
201
+ const sourcePromptsDir = join(projectRoot, 'prompts');
202
+
203
+ try {
204
+ // .aiexe/prompts 디렉토리 생성 (recursive로 .aiexe도 함께 생성)
205
+ await safeMkdir(targetPromptsDir, { recursive: true });
206
+ console.log(chalk.green(`✓ Created directory: ${targetPromptsDir}`));
207
+
208
+ // prompts 디렉토리의 모든 파일 복사
209
+ const files = await safeReaddir(sourcePromptsDir);
210
+ let copiedCount = 0;
211
+
212
+ for (const file of files) {
213
+ const sourcePath = join(sourcePromptsDir, file);
214
+ const targetPath = join(targetPromptsDir, file);
215
+
216
+ await safeCopyFile(sourcePath, targetPath);
217
+ console.log(chalk.gray(` Copied: ${file}`));
218
+ copiedCount++;
219
+ }
220
+
221
+ console.log(chalk.green(`\n✓ Successfully copied ${copiedCount} prompt file(s)`));
222
+ console.log(chalk.yellow(`\nYou can now customize prompts in: ${targetPromptsDir}`));
223
+ console.log(chalk.gray('These project-specific prompts will be used instead of the default ones.'));
224
+
225
+ } catch (error) {
226
+ console.log(chalk.red('\nStartup Failed: Prompt Initialization Error\n'));
227
+ console.log(chalk.yellow('Reason:'));
228
+ console.log(` Failed to initialize project-specific prompts: ${error.message}`);
229
+ console.log(chalk.yellow('\nSolution:'));
230
+ console.log(' 1. Check if you have write permissions in the current directory');
231
+ console.log(' 2. Ensure the prompts directory exists in the project root');
232
+ console.log(' 3. Verify disk space availability');
233
+ console.log(` 4. Try running with sudo if permission is denied\n`);
234
+ process.exit(1);
235
+ }
236
+
237
+ process.exit(0);
238
+ }
239
+
240
+ // Viewer 모드 처리
241
+ if (viewerMode) {
242
+ console.log(chalk.cyan(`Starting payload viewer on port ${viewerPort}...`));
243
+ const { startWebServer } = await import('./payload_viewer/web_server.js');
244
+ await startWebServer(viewerPort);
245
+ console.log(chalk.green(`✓ Payload viewer is running`));
246
+ console.log(chalk.yellow(`Press Ctrl+C to stop the server`));
247
+ // Keep process alive
248
+ await new Promise(() => { });
249
+ }
250
+
251
+ // ========================================
252
+ // 의존성 체크 (가장 먼저 수행)
253
+ // ========================================
254
+ // ripgrep, node, bash 등 필수 시스템 의존성을 확인
255
+ // Python은 선택사항이므로 여기서는 체크하지 않음 (나중에 settings 로드 후 확인)
256
+ const dependencyCheck = await checkDependencies({ skipPython: true });
257
+ if (!dependencyCheck.success) {
258
+ printDependencyError(dependencyCheck);
259
+ process.exit(1);
260
+ }
261
+
262
+ // 시스템 정보 수집 (의존성 체크 통과 후)
263
+ const initialSystemInfo = await getSystemInfo({ skipPython: true });
264
+
265
+ // 전역 설정
266
+ process.app_custom = {};
267
+ process.app_custom.__dirname = dirname(fileURLToPath(import.meta.url));
268
+ process.app_custom.dangerouslySkipPermissions = dangerouslySkipPermissions;
269
+ process.app_custom.pipeMode = pipeMode;
270
+ process.app_custom.debugMode = debugMode;
271
+ process.app_custom.systemInfo = initialSystemInfo;
272
+
273
+ // 개발 모드 감지: 현재 디렉토리에서 node index.js로 실행했는지 확인
274
+ // (글로벌 설치 후 aiexecode 명령으로 실행 시에는 다른 경로에서 실행됨)
275
+ const packageJsonPath = join(process.app_custom.__dirname, 'package.json');
276
+ const isDevelopment = existsSync(packageJsonPath) &&
277
+ process.app_custom.__dirname === dirname(fileURLToPath(import.meta.url));
278
+ process.env.IS_DEVELOPMENT = isDevelopment ? 'true' : 'false';
279
+
280
+ // Session ID 생성 함수 (16자리 hex)
281
+ function generateSessionID() {
282
+ const chars = '0123456789abcdef';
283
+ let sessionID = '';
284
+ for (let i = 0; i < 16; i++) {
285
+ sessionID += chars[Math.floor(Math.random() * chars.length)];
286
+ }
287
+ return sessionID;
288
+ }
289
+
290
+ // Session ID 설정
291
+ // --continue (interactive mode) 또는 --sessionid (pipe mode) 옵션 처리
292
+ const continueSessionId = shouldContinue || sessionIdOption;
293
+ if (continueSessionId) {
294
+ // 세션 이어가기 모드
295
+ if (typeof continueSessionId !== 'string' || continueSessionId.length !== 16 || !/^[0-9a-f]{16}$/.test(continueSessionId)) {
296
+ if (pipeMode) {
297
+ // Pipe mode에서는 stderr로 에러 출력
298
+ console.error('[ERROR] Invalid session ID format. Must be 16-char hex (e.g., abc1234567890def)');
299
+ process.exit(2);
300
+ } else {
301
+ console.log(chalk.red('\nStartup Failed: Invalid Session ID Format\n'));
302
+ console.log(chalk.yellow('Reason:'));
303
+ console.log(' The session ID must be a 16-character hexadecimal string (0-9, a-f).');
304
+ console.log(chalk.yellow('\nSolution:'));
305
+ console.log(' 1. Check your session ID format - it should look like: abc1234567890def');
306
+ console.log(' 2. Use the correct format with --continue option:');
307
+ console.log(chalk.cyan(' aiexecode --continue abc1234567890def "your mission"'));
308
+ console.log(' 3. Or start a new session without --continue option\n');
309
+ process.exit(1);
310
+ }
311
+ }
312
+ process.app_custom.sessionID = continueSessionId;
313
+ debugLog(`Continuing session ID: ${process.app_custom.sessionID}`);
314
+ } else {
315
+ // 새 세션 시작
316
+ process.app_custom.sessionID = generateSessionID();
317
+ debugLog(`New session ID: ${process.app_custom.sessionID}`);
318
+ }
319
+
320
+ // 임시 폴더 정리 (debug 모드가 아닐 때만)
321
+ // debug 모드에서는 로그를 유지해야 하므로 정리하지 않음
322
+ if (!debugMode) {
323
+ await safeRm(PAYLOAD_LOG_DIR, { recursive: true, force: true }); // 홈 디렉토리의 .aiexe/payload_log
324
+ await safeRm(DEBUG_LOG_DIR, { recursive: true, force: true }); // 홈 디렉토리의 .aiexe/debuglog
325
+ }
326
+
327
+ // 설정 로드 (시스템 정보 수집 전에 먼저 로드)
328
+ await ensureConfigDirectory();
329
+
330
+ // 초기 설정 확인 및 Setup Wizard 실행
331
+ // Pipe mode에서는 Setup Wizard 스킵 (설정이 없으면 에러)
332
+ const configured = await isConfigured();
333
+ if (!configured) {
334
+ if (pipeMode) {
335
+ console.error('[ERROR] Not configured. Run aiexecode interactively first to complete setup.');
336
+ process.exit(2);
337
+ }
338
+ const setupCompleted = await runSetupWizard();
339
+
340
+ if (!setupCompleted) {
341
+ console.log(chalk.red('\nStartup Failed: Setup Wizard Cancelled\n'));
342
+ console.log(chalk.yellow('Reason:'));
343
+ console.log(' Initial configuration was not completed.');
344
+ console.log(chalk.yellow('\nSolution:'));
345
+ console.log(' 1. Run aiexecode again to restart the setup wizard');
346
+ console.log(' 2. Complete all required configuration steps');
347
+ console.log(' 3. Provide valid API keys and settings');
348
+ console.log(` 4. Or manually edit the settings file: ${SETTINGS_FILE}\n`);
349
+ process.exit(1);
350
+ }
351
+ }
352
+
353
+ const settings = await loadSettings();
354
+
355
+ // --debug 옵션이 있으면 SHOW_API_PAYLOAD 활성화 (런타임에서만)
356
+ if (debugMode) {
357
+ settings.SHOW_API_PAYLOAD = true;
358
+ }
359
+
360
+ // Python 도구 사용 여부에 따른 시스템 정보 업데이트
361
+ const skipPython = settings?.TOOLS_ENABLED?.run_python_code === false;
362
+ if (!skipPython) {
363
+ // Python이 활성화된 경우 전체 시스템 정보 수집 (Python 포함)
364
+ process.app_custom.systemInfo = await getSystemInfo({ skipPython: false });
365
+
366
+ // Python 관련 경고 확인
367
+ const fullCheck = await checkDependencies({ skipPython: false });
368
+ if (fullCheck.warnings && fullCheck.warnings.length > 0) {
369
+ fullCheck.warnings.forEach(warning => {
370
+ if (warning.type === 'optional_command') {
371
+ debugLog(`Optional dependency missing: ${warning.command}`);
372
+ debugLog(` Install: ${warning.install.primary}`);
373
+ debugLog(` Disabled features: ${warning.disabledFeatures?.join(', ') || 'none'}`);
374
+ }
375
+ });
376
+ }
377
+ }
378
+
379
+ // 환경변수 설정
380
+ if (!process.env.API_KEY && settings?.API_KEY) {
381
+ process.env.API_KEY = settings.API_KEY;
382
+ }
383
+ if (!process.env.MODEL && settings?.MODEL) {
384
+ process.env.MODEL = settings.MODEL;
385
+ }
386
+ if (!process.env.REASONING_EFFORT && settings?.REASONING_EFFORT) {
387
+ process.env.REASONING_EFFORT = settings.REASONING_EFFORT;
388
+ }
389
+
390
+ // 최종 검증
391
+ if (!process.env.API_KEY) {
392
+ if (pipeMode) {
393
+ console.error('[ERROR] API_KEY not configured. Set it in ~/.aiexe/settings.json');
394
+ process.exit(2);
395
+ }
396
+ console.log(chalk.red('\nStartup Failed: Missing API Key\n'));
397
+ console.log(chalk.yellow('Reason:'));
398
+ console.log(' API_KEY is not configured in the settings.');
399
+ console.log(chalk.yellow('\nSolution:'));
400
+ console.log(' 1. Obtain an API key from Z.AI:');
401
+ console.log(chalk.cyan(' https://z.ai/manage-apikey/apikey-list'));
402
+ console.log(` 2. Add the API key to your settings file:`);
403
+ console.log(chalk.cyan(` ${SETTINGS_FILE}`));
404
+ console.log(' 3. Or run the setup wizard again by deleting the settings file');
405
+ console.log(' 4. Ensure the key is valid and has sufficient credits\n');
406
+ process.exit(1);
407
+ }
408
+
409
+ // Pipe mode에서는 mission이 필수
410
+ if (pipeMode && (!mission || !mission.trim())) {
411
+ console.error('[ERROR] Mission is required in pipe mode. Usage: node index.js -p "your mission"');
412
+ process.exit(2);
413
+ }
414
+
415
+ // ========================================
416
+ // MCP Integration 초기화
417
+ // ========================================
418
+ // MCP (Model Context Protocol) 서버들과의 연결을 설정하고 사용 가능한 도구 목록을 준비
419
+ // 프로그램 시작 시 백그라운드에서 비동기로 실행되어 UI 로딩을 블로킹하지 않음
420
+
421
+ let mcpIntegration = null; // MCP Integration 인스턴스 (서버 관리 및 도구 실행)
422
+ let mcpToolFunctions = {}; // MCP 도구 실행 함수들 (toolName -> async function)
423
+ let mcpToolSchemas = []; // MCP 도구 스키마들 (AI 모델에 전달할 도구 정의)
424
+
425
+ // MCP 초기화를 백그라운드에서 실행
426
+ // Promise만 저장하고 실제 UI 이벤트는 UI 시작 후에 발생시킨다
427
+ const mcpInitPromise = initializeMCPIntegration().then(async integration => {
428
+ // 초기화 성공 시 결과 저장
429
+ mcpIntegration = integration;
430
+
431
+ // MCP 도구 실행 함수들을 가져옴 (session.js에서 도구 실행 시 사용)
432
+ mcpToolFunctions = integration ? integration.getToolFunctions() : {};
433
+
434
+ // MCP 도구 스키마들을 가져옴 (orchestrator.js에서 AI에게 전달)
435
+ mcpToolSchemas = integration ? integration.getToolSchemas() : [];
436
+
437
+ // 초기화 결과 로깅
438
+ if (integration) {
439
+ const servers = integration.getConnectedServers();
440
+ const logLines = [];
441
+
442
+ logLines.push('');
443
+ logLines.push(`MCP INTEGRATION COMPLETE`);
444
+ logLines.push(`Timestamp: ${new Date().toISOString()}`);
445
+ logLines.push(`Total Servers: ${servers.length}`);
446
+ logLines.push(`Total Tools: ${Object.keys(mcpToolFunctions).length}`);
447
+ logLines.push(`Total Schemas: ${mcpToolSchemas.length}`);
448
+ logLines.push('');
449
+
450
+ // MCP 서버별 상세 정보
451
+ if (servers.length > 0) {
452
+ logLines.push('Connected MCP Servers:');
453
+ servers.forEach((server, idx) => {
454
+ if (idx > 0) logLines.push('');
455
+ logLines.push(`[${idx + 1}] ${server.name}`);
456
+ logLines.push(` Status: ${server.status}`);
457
+ logLines.push(` Tool Count: ${server.toolCount}`);
458
+ if (server.transport) {
459
+ logLines.push(` Transport: ${JSON.stringify(server.transport, null, 2).split('\n').join('\n ')}`);
460
+ }
461
+ if (server.tools && server.tools.length > 0) {
462
+ logLines.push(` Available Tools:`);
463
+ server.tools.forEach(tool => {
464
+ logLines.push(` - ${tool.name}: ${tool.description || 'No description'}`);
465
+ });
466
+ }
467
+ });
468
+ logLines.push('');
469
+ }
470
+
471
+ // mcpToolFunctions 구조 로깅
472
+ if (Object.keys(mcpToolFunctions).length > 0) {
473
+ logLines.push('MCP Tool Functions:');
474
+ Object.keys(mcpToolFunctions).forEach((toolName, idx) => {
475
+ const func = mcpToolFunctions[toolName];
476
+ logLines.push(` [${idx + 1}] ${toolName}`);
477
+ logLines.push(` Type: ${typeof func}`);
478
+ logLines.push(` Function Name: ${func?.name || 'anonymous'}`);
479
+ });
480
+ logLines.push('');
481
+ }
482
+
483
+ // mcpToolSchemas 구조 로깅
484
+ if (mcpToolSchemas.length > 0) {
485
+ logLines.push('MCP Tool Schemas:');
486
+ mcpToolSchemas.forEach((schema, idx) => {
487
+ logLines.push(` [${idx + 1}] ${schema.name}`);
488
+ logLines.push(` Description: ${schema.description || 'No description'}`);
489
+ if (schema.inputSchema) {
490
+ const props = schema.inputSchema.properties || {};
491
+ const propCount = Object.keys(props).length;
492
+ logLines.push(` Input Properties: ${propCount}`);
493
+ if (propCount > 0) {
494
+ Object.entries(props).forEach(([key, value]) => {
495
+ const required = schema.inputSchema.required?.includes(key) ? ' (required)' : '';
496
+ logLines.push(` - ${key}: ${value.type || 'unknown'}${required}`);
497
+ });
498
+ }
499
+ }
500
+ });
501
+ logLines.push('');
502
+ }
503
+
504
+ // 별도 파일에 기록 (createDebugLogger 사용)
505
+ const mcpLogger = createDebugLogger('mcp_initialization.log', 'MCP');
506
+ logLines.forEach(line => mcpLogger(line));
507
+
508
+ // 기존 디버그 로그에도 간략하게 출력
509
+ debugLog(`MCP integration complete: ${servers.length} server(s), ${Object.keys(mcpToolFunctions).length} tool(s)`);
510
+ servers.forEach(server => {
511
+ debugLog(` - ${server.name}: ${server.toolCount} tool(s) (${server.status})`);
512
+ });
513
+ }
514
+ return integration;
515
+ }).catch(err => {
516
+ // 초기화 실패 시에도 프로그램은 계속 실행 (MCP 없이도 동작)
517
+ debugLog(`MCP initialization failed: ${err.message}`);
518
+ return null;
519
+ });
520
+
521
+ // ========================================
522
+ // 커맨드 레지스트리 초기화
523
+ // ========================================
524
+ // 사용자가 입력하는 슬래시 커맨드들(/mcp, /exit 등)을 관리
525
+ const commandRegistry = new CommandRegistry();
526
+
527
+ // context 객체를 생성 - getter를 사용하여 mcpIntegration을 동적으로 참조
528
+ // 이렇게 하면 나중에 MCP가 초기화되어도 최신 값을 참조할 수 있음
529
+ const commandContext = {
530
+ commandRegistry,
531
+ get mcpIntegration() {
532
+ return mcpIntegration; // 현재 mcpIntegration 값을 반환 (null이거나 초기화된 값)
533
+ }
534
+ };
535
+
536
+ // src/commands/ 디렉토리의 모든 커맨드 파일들을 자동으로 로드하고 등록
537
+ await loadCommands(commandRegistry, commandContext);
538
+
539
+ // 커맨드 목록 준비 (내장 커맨드 + 스킬 + 커스텀 커맨드)
540
+ const builtinCommands = Array.from(commandRegistry.commands.entries()).map(([name, cmd]) => ({
541
+ name,
542
+ description: cmd.description || ''
543
+ }));
544
+
545
+ // 스킬 로드
546
+ const skills = await discoverAllSkills();
547
+ const skillCommands = skills.map(skill => ({
548
+ name: skill.name,
549
+ description: skill.description || `Skill: ${skill.name}`
550
+ }));
551
+
552
+ // 커스텀 커맨드 로드
553
+ const customCommands = await discoverAllCustomCommands();
554
+ const customCommandList = customCommands.map(cmd => ({
555
+ name: cmd.name,
556
+ description: cmd.description || `Command: ${cmd.name}`
557
+ }));
558
+
559
+ // 모두 합쳐서 commandList 생성 (중복 이름은 내장 커맨드 우선)
560
+ const commandNames = new Set(builtinCommands.map(c => c.name));
561
+ const commandList = [
562
+ ...builtinCommands,
563
+ ...skillCommands.filter(c => !commandNames.has(c.name)),
564
+ ...customCommandList.filter(c => !commandNames.has(c.name) && !skills.some(s => s.name === c.name))
565
+ ];
566
+
567
+ // Ink UI 시작
568
+ let uiInstance = null;
569
+ let currentMission = mission;
570
+
571
+ async function handleSubmit(text) {
572
+ if (!text || !text.trim()) return;
573
+
574
+ currentMission = text;
575
+
576
+ // 슬래시 커맨드 처리
577
+ if (isCommand(text)) {
578
+ try {
579
+ const result = await commandRegistry.execute(text);
580
+
581
+ // 커스텀 커맨드가 반환된 경우 AI에게 전달하여 실행
582
+ if (result && result.type === 'custom_command') {
583
+ debugLog(`Custom command invoked: ${result.commandName}`);
584
+ uiEvents.addSystemMessage(`📋 Command: ${result.commandName}`);
585
+
586
+ // 커맨드의 프롬프트를 미션으로 사용하여 세션 실행
587
+ await runSession({
588
+ mission: result.prompt,
589
+ maxIterations: 50,
590
+ mcpToolSchemas,
591
+ mcpToolFunctions
592
+ });
593
+ }
594
+ // 스킬이 반환된 경우 AI에게 전달하여 실행
595
+ else if (result && result.type === 'skill') {
596
+ debugLog(`Skill invoked: ${result.skillName}`);
597
+ uiEvents.addSkillInvoked(result.skillName);
598
+
599
+ // 스킬의 프롬프트를 미션으로 사용하여 세션 실행
600
+ await runSession({
601
+ mission: result.prompt,
602
+ maxIterations: 50,
603
+ mcpToolSchemas,
604
+ mcpToolFunctions
605
+ });
606
+ }
607
+ } catch (err) {
608
+ debugLog(`Command execution error: ${err.message}`);
609
+ uiEvents.addErrorMessage(`${err.message}\nType /help to see available commands`);
610
+ }
611
+ return;
612
+ }
613
+
614
+ // 세션 실행 (AI Agent의 미션 수행)
615
+ // 시작/종료 알림 및 저장은 runSession 내부에서 처리
616
+ try {
617
+ // 파일 참조 파싱 (@경로 -> 참조된 파일 목록으로 변환)
618
+ const parsed = await parseFileReferences(text);
619
+ const missionText = parsed.hasReferences ? parsed.transformedMessage : text;
620
+
621
+ await runSession({
622
+ mission: missionText, // 변환된 미션 (파일 참조 포함 시)
623
+ maxIterations: 50, // 최대 반복 횟수
624
+ mcpToolSchemas, // AI 모델에 전달할 MCP 도구 스키마들
625
+ mcpToolFunctions // 실제 MCP 도구 실행 함수들
626
+ });
627
+ } catch (err) {
628
+ // API 인증 에러 등 세션 실행 중 발생한 에러를 history에 표시
629
+ const errorMessage = err?.message || String(err);
630
+ const errorCode = err?.code || 'UNKNOWN';
631
+ const errorStatus = err?.status || 'N/A';
632
+ const errorType = err?.type || err?.constructor?.name || 'Error';
633
+ const errorStack = err?.stack || 'No stack trace available';
634
+
635
+ // debug 설정 확인
636
+ const settings = await loadSettings().catch(() => ({}));
637
+ const isDebugMode = settings?.SHOW_API_PAYLOAD === true;
638
+
639
+ // 인증 에러인 경우 설정 파일 안내
640
+ if (err?.code === 'invalid_api_key' || err?.status === 401) {
641
+ if (isDebugMode) {
642
+ const consolidatedErrorMessage = [
643
+ `[Main] Authentication failed: ${errorMessage}`,
644
+ ` ├─ Code: ${errorCode}`,
645
+ ` ├─ Status: ${errorStatus}`,
646
+ ` ├─ Type: ${errorType}`,
647
+ ` └─ Stack trace: ${errorStack}`
648
+ ].join('\n');
649
+ uiEvents.addErrorMessage(consolidatedErrorMessage);
650
+ } else {
651
+ uiEvents.addErrorMessage('[Main] API 키가 유효하지 않습니다.');
652
+ }
653
+ uiEvents.addSystemMessage(`Please check your API key in ${SETTINGS_FILE}`);
654
+ } else {
655
+ // 일반 에러
656
+ if (isDebugMode) {
657
+ const consolidatedErrorMessage = [
658
+ `[Main] Session execution error: ${errorMessage}`,
659
+ ` ├─ Code: ${errorCode}`,
660
+ ` ├─ Status: ${errorStatus}`,
661
+ ` ├─ Type: ${errorType}`,
662
+ ` ├─ Error Object: ${JSON.stringify(err, null, 2)}`,
663
+ ` └─ Stack trace: ${errorStack}`
664
+ ].join('\n');
665
+ uiEvents.addErrorMessage(consolidatedErrorMessage);
666
+ } else {
667
+ // 사용자 친화적 에러 메시지
668
+ const statusNum = parseInt(errorStatus) || parseInt(err?.status);
669
+ let userFriendlyMessage;
670
+
671
+ if (statusNum === 503 || errorMessage.includes('503')) {
672
+ userFriendlyMessage = 'AI 서버가 일시적으로 응답하지 않습니다. 잠시 후 다시 시도해주세요.';
673
+ } else if (statusNum === 500 || errorMessage.includes('500')) {
674
+ userFriendlyMessage = 'AI 서버에서 오류가 발생했습니다. 잠시 후 다시 시도해주세요.';
675
+ } else if (statusNum === 429) {
676
+ userFriendlyMessage = '요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요.';
677
+ } else {
678
+ userFriendlyMessage = '오류가 발생했습니다. 문제가 지속되면 /debug on 으로 상세 정보를 확인하세요.';
679
+ }
680
+
681
+ uiEvents.addErrorMessage(`[Main] ${userFriendlyMessage}`);
682
+ }
683
+ }
684
+ }
685
+ }
686
+
687
+ function handleClearScreen() {
688
+ console.clear();
689
+ }
690
+
691
+ async function handleExit() {
692
+ await performExit({
693
+ mcpIntegration,
694
+ uiInstance,
695
+ showMessages: true
696
+ });
697
+ }
698
+
699
+ // ========================================
700
+ // Pipe Mode 처리
701
+ // ========================================
702
+ // UI 없이 직접 세션 실행하고 sessionid 출력 후 종료
703
+ if (pipeMode) {
704
+ debugLog(`[PipeMode] Starting pipe mode execution`);
705
+ debugLog(`[PipeMode] Mission: ${mission}`);
706
+ debugLog(`[PipeMode] SessionID: ${process.app_custom.sessionID}`);
707
+ debugLog(`[PipeMode] Debug: ${debugMode}`);
708
+
709
+ try {
710
+ // MCP 초기화 완료 대기
711
+ await mcpInitPromise;
712
+ debugLog(`[PipeMode] MCP initialization complete`);
713
+
714
+ // 이전 세션 로드 (--sessionid 옵션)
715
+ let previousSessions = null;
716
+ if (sessionIdOption) {
717
+ previousSessions = await loadPreviousSessions(process.app_custom.sessionID);
718
+ if (previousSessions && previousSessions.length > 0) {
719
+ debugLog(`[PipeMode] Loaded ${previousSessions.length} previous session(s)`);
720
+ } else {
721
+ debugLog(`[PipeMode] No previous session found for ID: ${process.app_custom.sessionID}`);
722
+ }
723
+ }
724
+
725
+ // 파일 참조 파싱
726
+ const parsed = await parseFileReferences(mission);
727
+ const missionText = parsed.hasReferences ? parsed.transformedMessage : mission;
728
+
729
+ // 세션 실행
730
+ const result = await runSession({
731
+ mission: missionText,
732
+ maxIterations: 50,
733
+ mcpToolSchemas,
734
+ mcpToolFunctions,
735
+ previousSessions
736
+ });
737
+
738
+ // stdout에 sessionid만 출력 (다른 모든 출력은 stderr나 로그 파일로)
739
+ console.log(process.app_custom.sessionID);
740
+
741
+ // MCP 정리
742
+ if (mcpIntegration) {
743
+ await mcpIntegration.cleanup();
744
+ }
745
+
746
+ // 종료 (성공=0, 미션 미완료=1)
747
+ process.exit(result?.mission_solved ? 0 : 1);
748
+ } catch (error) {
749
+ console.error(`[ERROR] ${error.message}`);
750
+ debugLog(`[PipeMode] Fatal error: ${error.stack}`);
751
+
752
+ // MCP 정리 시도
753
+ if (mcpIntegration) {
754
+ await mcpIntegration.cleanup().catch(() => {});
755
+ }
756
+
757
+ process.exit(2);
758
+ }
759
+ }
760
+
761
+ // ========================================
762
+ // Interactive Mode (UI)
763
+ // ========================================
764
+
765
+ // 히스토리 복원 (--continue 옵션)
766
+ let initialHistory = [];
767
+ if (continueSessionId) {
768
+ const previousSessions = await loadPreviousSessions(process.app_custom.sessionID);
769
+ if (previousSessions && previousSessions.length > 0) {
770
+ debugLog(`Loaded ${previousSessions.length} session(s) from history`);
771
+
772
+ // 디버깅: 세션 데이터 확인
773
+ previousSessions.forEach((session, idx) => {
774
+ debugLog(` Session ${idx}: mission="${session.mission}", toolUsageHistory=${session.toolUsageHistory?.length || 0} items`);
775
+ if (session.toolUsageHistory && session.toolUsageHistory.length > 0) {
776
+ session.toolUsageHistory.slice(0, 2).forEach((tool, tidx) => {
777
+ const argsStr = JSON.stringify(tool.args || {});
778
+ debugLog(` Tool ${tidx}: toolName="${tool.toolName}", args=${argsStr.substring(0, 50)}`);
779
+ });
780
+ }
781
+ });
782
+
783
+ initialHistory = reconstructUIHistory(previousSessions);
784
+ debugLog(` ${initialHistory.length} history items reconstructed`);
785
+
786
+ // 디버깅: 복원된 항목 확인
787
+ initialHistory.slice(0, 5).forEach((item, idx) => {
788
+ const textPreview = item.text ? (typeof item.text === 'string' ? item.text.substring(0, 50) : JSON.stringify(item.text).substring(0, 50)) : 'NO TEXT';
789
+ debugLog(` [${idx}] type=${item.type}, toolName=${item.toolName || 'N/A'}, text=${textPreview}`);
790
+ });
791
+ } else {
792
+ debugLog(`No previous session history found for session ID: ${process.app_custom.sessionID}`);
793
+ debugLog(`Starting with empty history. The session will be saved to ${process.cwd()}/.aiexe/${process.app_custom.sessionID}/`);
794
+ }
795
+ } else {
796
+ await deleteHistoryFile(process.app_custom.sessionID);
797
+ }
798
+
799
+ // 버전 체크 (비동기, 백그라운드에서 실행하되 Promise만 저장, 이벤트는 UI 시작 후에 발생)
800
+ let updateInfo = null;
801
+
802
+ const versionCheckPromise = checkForUpdates(VERSION).then(info => {
803
+ updateInfo = info;
804
+ if (info.updateAvailable) {
805
+ debugLog(`Update available: ${VERSION} → ${info.remoteVersion}`);
806
+ } else {
807
+ debugLog(`No update available (current: ${VERSION})`);
808
+ }
809
+ return info;
810
+ }).catch(err => {
811
+ debugLog(`Version check failed: ${err.message}`);
812
+ return null;
813
+ });
814
+
815
+ // UI 시작
816
+ const currentModel = await getModelForProvider();
817
+ const currentReasoningEffort = settings?.REASONING_EFFORT || process.env.REASONING_EFFORT;
818
+ uiInstance = startUI({
819
+ onSubmit: handleSubmit,
820
+ onClearScreen: handleClearScreen,
821
+ onExit: handleExit,
822
+ commands: commandList,
823
+ model: currentModel,
824
+ version: VERSION,
825
+ initialHistory,
826
+ reasoningEffort: currentReasoningEffort,
827
+ updateInfo: null // 초기에는 null, 나중에 업데이트됨
828
+ });
829
+
830
+ // ========================================
831
+ // 백그라운드 초기화 태스크 UI 연동
832
+ // ========================================
833
+ // UI가 시작된 후 백그라운드에서 실행 중인 초기화 작업들을 사용자에게 표시
834
+
835
+ // 버전 체크 로딩 표시
836
+ uiEvents.emit('loading:task_add', {
837
+ id: 'version_check',
838
+ text: 'Checking for updates...'
839
+ });
840
+
841
+ // MCP 초기화 로딩 표시
842
+ uiEvents.emit('loading:task_add', {
843
+ id: 'mcp_init',
844
+ text: 'Initializing MCP servers...'
845
+ });
846
+
847
+ // 백그라운드 초기화 완료 시 UI에 이벤트 발생
848
+ // Promise가 이미 완료된 경우에도 then이 실행되어 UI가 업데이트됨
849
+ // catch에서 null을 반환하므로 항상 resolved 상태로 then이 실행됨
850
+
851
+ // 버전 체크 완료 이벤트
852
+ versionCheckPromise.then(info => {
853
+ uiEvents.emit('version:update', { updateInfo: info });
854
+ });
855
+
856
+ // MCP 초기화 완료 이벤트
857
+ // 이 시점에서 MCP 서버들과의 연결이 완료되고 도구 목록이 준비됨
858
+ mcpInitPromise.then(integration => {
859
+ uiEvents.emit('mcp:initialized', { integration });
860
+ });
861
+
862
+ // 초기 미션이 있으면 자동 실행
863
+ if (currentMission && currentMission.trim()) {
864
+ handleSubmit(currentMission);
865
+ }
866
+
867
+ // 종료 처리 (Ctrl+C) - handleExit를 호출하도록 변경
868
+ process.on('SIGINT', handleExit);
869
+
870
+ // UI가 종료될 때까지 대기
871
+ await uiInstance.waitUntilExit();
872
+
873
+ // ========================================
874
+ // 프로그램 종료 시 MCP 정리
875
+ // ========================================
876
+ // 모든 MCP 서버와의 연결을 정상적으로 종료하고 리소스 정리
877
+ if (mcpIntegration) {
878
+ await mcpIntegration.cleanup();
879
+ }