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.
- package/LICENSE +68 -0
- package/README.md +347 -0
- package/config_template/mcp_config.json +3 -0
- package/config_template/package_name_store.json +5 -0
- package/config_template/settings.json +5 -0
- package/index.js +879 -0
- package/mcp-agent-lib/example/01-basic-usage.js +82 -0
- package/mcp-agent-lib/example/02-quick-start.js +52 -0
- package/mcp-agent-lib/example/03-http-server.js +76 -0
- package/mcp-agent-lib/example/04-multiple-servers.js +117 -0
- package/mcp-agent-lib/example/05-error-handling.js +116 -0
- package/mcp-agent-lib/example/06-resources-and-prompts.js +174 -0
- package/mcp-agent-lib/example/07-advanced-configuration.js +191 -0
- package/mcp-agent-lib/example/08-real-world-chatbot.js +331 -0
- package/mcp-agent-lib/example/README.md +346 -0
- package/mcp-agent-lib/index.js +19 -0
- package/mcp-agent-lib/init.sh +3 -0
- package/mcp-agent-lib/package-lock.json +1216 -0
- package/mcp-agent-lib/package.json +53 -0
- package/mcp-agent-lib/sampleFastMCPClient/client.py +25 -0
- package/mcp-agent-lib/sampleFastMCPClient/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServer/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServer/server.py +12 -0
- package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/run.sh +3 -0
- package/mcp-agent-lib/sampleFastMCPServerElicitationRequest/server.py +43 -0
- package/mcp-agent-lib/sampleFastMCPServerRootsRequest/server.py +63 -0
- package/mcp-agent-lib/sampleMCPHost/index.js +386 -0
- package/mcp-agent-lib/sampleMCPHost/mcp_config.json +24 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/elicitation.js +151 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/index.js +166 -0
- package/mcp-agent-lib/sampleMCPHostFeatures/roots.js +197 -0
- package/mcp-agent-lib/src/mcp_client.js +1860 -0
- package/mcp-agent-lib/src/mcp_message_logger.js +517 -0
- package/package.json +72 -0
- package/payload_viewer/out/404/index.html +1 -0
- package/payload_viewer/out/404.html +1 -0
- package/payload_viewer/out/_next/static/chunks/060f9a97930f3d04.js +1 -0
- package/payload_viewer/out/_next/static/chunks/103c802c8f4a5ea1.js +1 -0
- package/payload_viewer/out/_next/static/chunks/16474fd6c6910c45.js +1 -0
- package/payload_viewer/out/_next/static/chunks/17722e3ac4e00587.js +1 -0
- package/payload_viewer/out/_next/static/chunks/305b077a9873cf54.js +1 -0
- package/payload_viewer/out/_next/static/chunks/4c1d05c6741c2bdd.js +5 -0
- package/payload_viewer/out/_next/static/chunks/538cc02e54714b23.js +1 -0
- package/payload_viewer/out/_next/static/chunks/6251fa5907d2b226.js +5 -0
- package/payload_viewer/out/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/payload_viewer/out/_next/static/chunks/b6c0459f3789d25c.js +1 -0
- package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
- package/payload_viewer/out/_next/static/chunks/bd2dcf98c9b362f6.js +1 -0
- package/payload_viewer/out/_next/static/chunks/c8a542ae21335479.js +1 -0
- package/payload_viewer/out/_next/static/chunks/cdd12d5c1a5a6064.js +1 -0
- package/payload_viewer/out/_next/static/chunks/e411019f55d87c42.js +1 -0
- package/payload_viewer/out/_next/static/chunks/e60ef129113f6e24.js +1 -0
- package/payload_viewer/out/_next/static/chunks/f1ac9047ac4a3fde.js +1 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-0ac29803ce3c3c7a.js +3 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-89db4c64206a73e4.js +3 -0
- package/payload_viewer/out/_next/static/chunks/turbopack-a5b8235fa59d7119.js +3 -0
- package/payload_viewer/out/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/payload_viewer/out/_next/static/media/favicon.0b3bf435.ico +0 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_buildManifest.js +14 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_clientMiddlewareManifest.json +1 -0
- package/payload_viewer/out/_next/static/uqQYtKpKV7kCSkUbLgdfJ/_ssgManifest.js +1 -0
- package/payload_viewer/out/favicon.ico +0 -0
- package/payload_viewer/out/file.svg +1 -0
- package/payload_viewer/out/globe.svg +1 -0
- package/payload_viewer/out/index.html +1 -0
- package/payload_viewer/out/index.txt +23 -0
- package/payload_viewer/out/next.svg +1 -0
- package/payload_viewer/out/vercel.svg +1 -0
- package/payload_viewer/out/window.svg +1 -0
- package/payload_viewer/web_server.js +861 -0
- package/prompts/completion_judge.txt +128 -0
- package/prompts/orchestrator.txt +1213 -0
- package/src/LLMClient/client.js +1375 -0
- package/src/LLMClient/converters/input-normalizer.js +238 -0
- package/src/LLMClient/converters/responses-to-claude.js +503 -0
- package/src/LLMClient/converters/responses-to-gemini.js +648 -0
- package/src/LLMClient/converters/responses-to-ollama.js +348 -0
- package/src/LLMClient/converters/responses-to-zai.js +667 -0
- package/src/LLMClient/errors.js +398 -0
- package/src/LLMClient/index.js +36 -0
- package/src/ai_based/completion_judge.js +421 -0
- package/src/ai_based/orchestrator.js +527 -0
- package/src/ai_based/pip_package_installer.js +173 -0
- package/src/ai_based/pip_package_lookup.js +197 -0
- package/src/cli/mcp_cli.js +70 -0
- package/src/cli/mcp_commands.js +255 -0
- package/src/commands/agents.js +18 -0
- package/src/commands/apikey.js +55 -0
- package/src/commands/bg.js +140 -0
- package/src/commands/commands.js +56 -0
- package/src/commands/debug.js +54 -0
- package/src/commands/exit.js +19 -0
- package/src/commands/help.js +35 -0
- package/src/commands/mcp.js +128 -0
- package/src/commands/model.js +176 -0
- package/src/commands/setup.js +13 -0
- package/src/commands/skills.js +51 -0
- package/src/commands/tools.js +165 -0
- package/src/commands/viewer.js +147 -0
- package/src/config/ai_models.js +312 -0
- package/src/config/config.js +10 -0
- package/src/config/constants.js +71 -0
- package/src/config/feature_flags.js +15 -0
- package/src/frontend/App.js +1263 -0
- package/src/frontend/README.md +81 -0
- package/src/frontend/components/AutocompleteMenu.js +47 -0
- package/src/frontend/components/BackgroundProcessList.js +175 -0
- package/src/frontend/components/BlankLine.js +62 -0
- package/src/frontend/components/ConversationItem.js +893 -0
- package/src/frontend/components/CurrentModelView.js +43 -0
- package/src/frontend/components/FileDiffViewer.js +616 -0
- package/src/frontend/components/Footer.js +25 -0
- package/src/frontend/components/Header.js +42 -0
- package/src/frontend/components/HelpView.js +154 -0
- package/src/frontend/components/Input.js +344 -0
- package/src/frontend/components/LoadingIndicator.js +31 -0
- package/src/frontend/components/ModelListView.js +49 -0
- package/src/frontend/components/ModelUpdatedView.js +22 -0
- package/src/frontend/components/SessionSpinner.js +66 -0
- package/src/frontend/components/SetupWizard.js +242 -0
- package/src/frontend/components/StreamOutput.js +34 -0
- package/src/frontend/components/TodoList.js +56 -0
- package/src/frontend/components/ToolApprovalPrompt.js +452 -0
- package/src/frontend/design/themeColors.js +42 -0
- package/src/frontend/hooks/useCompletion.js +84 -0
- package/src/frontend/hooks/useFileCompletion.js +467 -0
- package/src/frontend/hooks/useKeypress.js +145 -0
- package/src/frontend/index.js +65 -0
- package/src/frontend/utils/GridRenderer.js +140 -0
- package/src/frontend/utils/InlineFormatter.js +156 -0
- package/src/frontend/utils/diffUtils.js +235 -0
- package/src/frontend/utils/inputBuffer.js +441 -0
- package/src/frontend/utils/markdownParser.js +377 -0
- package/src/frontend/utils/outputRedirector.js +47 -0
- package/src/frontend/utils/renderInkComponent.js +42 -0
- package/src/frontend/utils/syntaxHighlighter.js +149 -0
- package/src/frontend/utils/toolUIFormatter.js +261 -0
- package/src/system/agents_loader.js +170 -0
- package/src/system/ai_request.js +737 -0
- package/src/system/background_process.js +317 -0
- package/src/system/code_executer.js +1233 -0
- package/src/system/command_loader.js +40 -0
- package/src/system/command_parser.js +133 -0
- package/src/system/conversation_state.js +265 -0
- package/src/system/conversation_trimmer.js +265 -0
- package/src/system/custom_command_loader.js +395 -0
- package/src/system/file_integrity.js +466 -0
- package/src/system/import_analyzer.py +174 -0
- package/src/system/log.js +82 -0
- package/src/system/mcp_integration.js +304 -0
- package/src/system/output_helper.js +89 -0
- package/src/system/session.js +1393 -0
- package/src/system/session_memory.js +481 -0
- package/src/system/skill_loader.js +324 -0
- package/src/system/system_info.js +483 -0
- package/src/system/tool_approval.js +160 -0
- package/src/system/tool_registry.js +184 -0
- package/src/system/ui_events.js +279 -0
- package/src/tools/code_editor.js +792 -0
- package/src/tools/file_reader.js +385 -0
- package/src/tools/glob.js +263 -0
- package/src/tools/response_message.js +30 -0
- package/src/tools/ripgrep.js +554 -0
- package/src/tools/skill_tool.js +122 -0
- package/src/tools/todo_write.js +182 -0
- package/src/tools/web_download.py +74 -0
- package/src/tools/web_downloader.js +83 -0
- package/src/util/clone.js +174 -0
- package/src/util/config.js +203 -0
- package/src/util/config_migration.js +174 -0
- package/src/util/debug_log.js +49 -0
- package/src/util/exit_handler.js +53 -0
- package/src/util/file_reference_parser.js +132 -0
- package/src/util/mcp_config_manager.js +159 -0
- package/src/util/output_formatter.js +50 -0
- package/src/util/path_helper.js +27 -0
- package/src/util/path_validator.js +178 -0
- package/src/util/prompt_loader.js +184 -0
- package/src/util/rag_helper.js +101 -0
- package/src/util/safe_fs.js +645 -0
- package/src/util/setup_wizard.js +62 -0
- package/src/util/text_formatter.js +33 -0
- 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
|
+
}
|